Compare commits
14 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef8be408d3 | ||
|
|
492040bd9c | ||
|
|
797e351bf8 | ||
|
|
c60b0b8432 | ||
|
|
a77e9a27b0 | ||
|
|
fd4e994de3 | ||
|
|
4841440382 | ||
|
|
189bd1980c | ||
|
|
ac3ba9a33e | ||
|
|
90d06acfed | ||
|
|
7af82ca022 | ||
|
|
fe392fff24 | ||
|
|
e28fd3d0a5 | ||
|
|
c4ac05c82c |
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -43,6 +43,3 @@
|
||||
[submodule "externals/zstd"]
|
||||
path = externals/zstd
|
||||
url = https://github.com/facebook/zstd
|
||||
[submodule "sirit"]
|
||||
path = externals/sirit
|
||||
url = https://github.com/ReinUsesLisp/sirit
|
||||
|
||||
5
externals/CMakeLists.txt
vendored
5
externals/CMakeLists.txt
vendored
@@ -72,11 +72,6 @@ if (USE_DISCORD_PRESENCE)
|
||||
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
|
||||
endif()
|
||||
|
||||
# Sirit
|
||||
if (ENABLE_VULKAN)
|
||||
add_subdirectory(sirit)
|
||||
endif()
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
# LibreSSL
|
||||
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
|
||||
|
||||
1
externals/sirit
vendored
1
externals/sirit
vendored
Submodule externals/sirit deleted from f7c4b07a7e
@@ -20,7 +20,7 @@ struct ScopeExitHelper {
|
||||
|
||||
template <typename Func>
|
||||
ScopeExitHelper<Func> ScopeExit(Func&& func) {
|
||||
return ScopeExitHelper<Func>(std::forward<Func>(func));
|
||||
return ScopeExitHelper<Func>(std::move(func));
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
|
||||
@@ -21,6 +21,11 @@
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <cstdlib>
|
||||
#elif defined(__linux__)
|
||||
#include <byteswap.h>
|
||||
#elif defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || \
|
||||
defined(__NetBSD__) || defined(__OpenBSD__)
|
||||
#include <sys/endian.h>
|
||||
#endif
|
||||
#include <cstring>
|
||||
#include "common/common_types.h"
|
||||
@@ -57,49 +62,86 @@
|
||||
namespace Common {
|
||||
|
||||
#ifdef _MSC_VER
|
||||
[[nodiscard]] inline u16 swap16(u16 data) noexcept {
|
||||
return _byteswap_ushort(data);
|
||||
inline u16 swap16(u16 _data) {
|
||||
return _byteswap_ushort(_data);
|
||||
}
|
||||
[[nodiscard]] inline u32 swap32(u32 data) noexcept {
|
||||
return _byteswap_ulong(data);
|
||||
inline u32 swap32(u32 _data) {
|
||||
return _byteswap_ulong(_data);
|
||||
}
|
||||
[[nodiscard]] inline u64 swap64(u64 data) noexcept {
|
||||
return _byteswap_uint64(data);
|
||||
inline u64 swap64(u64 _data) {
|
||||
return _byteswap_uint64(_data);
|
||||
}
|
||||
#elif defined(__clang__) || defined(__GNUC__)
|
||||
#if defined(__Bitrig__) || defined(__OpenBSD__)
|
||||
#elif defined(ARCHITECTURE_ARM) && (__ARM_ARCH >= 6)
|
||||
inline u16 swap16(u16 _data) {
|
||||
u32 data = _data;
|
||||
__asm__("rev16 %0, %1\n" : "=l"(data) : "l"(data));
|
||||
return (u16)data;
|
||||
}
|
||||
inline u32 swap32(u32 _data) {
|
||||
__asm__("rev %0, %1\n" : "=l"(_data) : "l"(_data));
|
||||
return _data;
|
||||
}
|
||||
inline u64 swap64(u64 _data) {
|
||||
return ((u64)swap32(_data) << 32) | swap32(_data >> 32);
|
||||
}
|
||||
#elif __linux__
|
||||
inline u16 swap16(u16 _data) {
|
||||
return bswap_16(_data);
|
||||
}
|
||||
inline u32 swap32(u32 _data) {
|
||||
return bswap_32(_data);
|
||||
}
|
||||
inline u64 swap64(u64 _data) {
|
||||
return bswap_64(_data);
|
||||
}
|
||||
#elif __APPLE__
|
||||
inline __attribute__((always_inline)) u16 swap16(u16 _data) {
|
||||
return (_data >> 8) | (_data << 8);
|
||||
}
|
||||
inline __attribute__((always_inline)) u32 swap32(u32 _data) {
|
||||
return __builtin_bswap32(_data);
|
||||
}
|
||||
inline __attribute__((always_inline)) u64 swap64(u64 _data) {
|
||||
return __builtin_bswap64(_data);
|
||||
}
|
||||
#elif defined(__Bitrig__) || defined(__OpenBSD__)
|
||||
// redefine swap16, swap32, swap64 as inline functions
|
||||
#undef swap16
|
||||
#undef swap32
|
||||
#undef swap64
|
||||
#endif
|
||||
[[nodiscard]] inline u16 swap16(u16 data) noexcept {
|
||||
return __builtin_bswap16(data);
|
||||
inline u16 swap16(u16 _data) {
|
||||
return __swap16(_data);
|
||||
}
|
||||
[[nodiscard]] inline u32 swap32(u32 data) noexcept {
|
||||
return __builtin_bswap32(data);
|
||||
inline u32 swap32(u32 _data) {
|
||||
return __swap32(_data);
|
||||
}
|
||||
[[nodiscard]] inline u64 swap64(u64 data) noexcept {
|
||||
return __builtin_bswap64(data);
|
||||
inline u64 swap64(u64 _data) {
|
||||
return __swap64(_data);
|
||||
}
|
||||
#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__)
|
||||
inline u16 swap16(u16 _data) {
|
||||
return bswap16(_data);
|
||||
}
|
||||
inline u32 swap32(u32 _data) {
|
||||
return bswap32(_data);
|
||||
}
|
||||
inline u64 swap64(u64 _data) {
|
||||
return bswap64(_data);
|
||||
}
|
||||
#else
|
||||
// Generic implementation.
|
||||
[[nodiscard]] inline u16 swap16(u16 data) noexcept {
|
||||
// Slow generic implementation.
|
||||
inline u16 swap16(u16 data) {
|
||||
return (data >> 8) | (data << 8);
|
||||
}
|
||||
[[nodiscard]] inline u32 swap32(u32 data) noexcept {
|
||||
return ((data & 0xFF000000U) >> 24) | ((data & 0x00FF0000U) >> 8) |
|
||||
((data & 0x0000FF00U) << 8) | ((data & 0x000000FFU) << 24);
|
||||
inline u32 swap32(u32 data) {
|
||||
return (swap16(data) << 16) | swap16(data >> 16);
|
||||
}
|
||||
[[nodiscard]] inline u64 swap64(u64 data) noexcept {
|
||||
return ((data & 0xFF00000000000000ULL) >> 56) | ((data & 0x00FF000000000000ULL) >> 40) |
|
||||
((data & 0x0000FF0000000000ULL) >> 24) | ((data & 0x000000FF00000000ULL) >> 8) |
|
||||
((data & 0x00000000FF000000ULL) << 8) | ((data & 0x0000000000FF0000ULL) << 24) |
|
||||
((data & 0x000000000000FF00ULL) << 40) | ((data & 0x00000000000000FFULL) << 56);
|
||||
inline u64 swap64(u64 data) {
|
||||
return ((u64)swap32(data) << 32) | swap32(data >> 32);
|
||||
}
|
||||
#endif
|
||||
|
||||
[[nodiscard]] inline float swapf(float f) noexcept {
|
||||
inline float swapf(float f) {
|
||||
static_assert(sizeof(u32) == sizeof(float), "float must be the same size as uint32_t.");
|
||||
|
||||
u32 value;
|
||||
@@ -111,7 +153,7 @@ namespace Common {
|
||||
return f;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline double swapd(double f) noexcept {
|
||||
inline double swapd(double f) {
|
||||
static_assert(sizeof(u64) == sizeof(double), "double must be the same size as uint64_t.");
|
||||
|
||||
u64 value;
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
@@ -100,7 +99,7 @@ public:
|
||||
}
|
||||
|
||||
void CallSVC(u32 swi) override {
|
||||
Kernel::CallSVC(parent.system, swi);
|
||||
Kernel::CallSVC(swi);
|
||||
}
|
||||
|
||||
void AddTicks(u64 ticks) override {
|
||||
@@ -113,14 +112,14 @@ public:
|
||||
// Always execute at least one tick.
|
||||
amortized_ticks = std::max<u64>(amortized_ticks, 1);
|
||||
|
||||
parent.system.CoreTiming().AddTicks(amortized_ticks);
|
||||
parent.core_timing.AddTicks(amortized_ticks);
|
||||
num_interpreted_instructions = 0;
|
||||
}
|
||||
u64 GetTicksRemaining() override {
|
||||
return std::max(parent.system.CoreTiming().GetDowncount(), 0);
|
||||
return std::max(parent.core_timing.GetDowncount(), 0);
|
||||
}
|
||||
u64 GetCNTPCT() override {
|
||||
return Timing::CpuCyclesToClockCycles(parent.system.CoreTiming().GetTicks());
|
||||
return Timing::CpuCyclesToClockCycles(parent.core_timing.GetTicks());
|
||||
}
|
||||
|
||||
ARM_Dynarmic& parent;
|
||||
@@ -130,7 +129,7 @@ public:
|
||||
};
|
||||
|
||||
std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
|
||||
auto* current_process = system.Kernel().CurrentProcess();
|
||||
auto* current_process = Core::CurrentProcess();
|
||||
auto** const page_table = current_process->VMManager().page_table.pointers.data();
|
||||
|
||||
Dynarmic::A64::UserConfig config;
|
||||
@@ -172,10 +171,10 @@ void ARM_Dynarmic::Step() {
|
||||
cb->InterpreterFallback(jit->GetPC(), 1);
|
||||
}
|
||||
|
||||
ARM_Dynarmic::ARM_Dynarmic(System& system, ExclusiveMonitor& exclusive_monitor,
|
||||
ARM_Dynarmic::ARM_Dynarmic(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor,
|
||||
std::size_t core_index)
|
||||
: cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), inner_unicorn{system},
|
||||
core_index{core_index}, system{system},
|
||||
: cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), inner_unicorn{core_timing},
|
||||
core_index{core_index}, core_timing{core_timing},
|
||||
exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {
|
||||
ThreadContext ctx{};
|
||||
inner_unicorn.SaveContext(ctx);
|
||||
|
||||
@@ -12,15 +12,19 @@
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/arm/unicorn/arm_unicorn.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ARM_Dynarmic_Callbacks;
|
||||
class DynarmicExclusiveMonitor;
|
||||
class System;
|
||||
|
||||
class ARM_Dynarmic final : public ARM_Interface {
|
||||
public:
|
||||
ARM_Dynarmic(System& system, ExclusiveMonitor& exclusive_monitor, std::size_t core_index);
|
||||
ARM_Dynarmic(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor,
|
||||
std::size_t core_index);
|
||||
~ARM_Dynarmic() override;
|
||||
|
||||
void MapBackingMemory(VAddr address, std::size_t size, u8* memory,
|
||||
@@ -59,7 +63,7 @@ private:
|
||||
ARM_Unicorn inner_unicorn;
|
||||
|
||||
std::size_t core_index;
|
||||
System& system;
|
||||
Timing::CoreTiming& core_timing;
|
||||
DynarmicExclusiveMonitor& exclusive_monitor;
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
@@ -48,6 +49,20 @@ static void CodeHook(uc_engine* uc, uint64_t address, uint32_t size, void* user_
|
||||
}
|
||||
}
|
||||
|
||||
static void InterruptHook(uc_engine* uc, u32 intNo, void* user_data) {
|
||||
u32 esr{};
|
||||
CHECKED(uc_reg_read(uc, UC_ARM64_REG_ESR, &esr));
|
||||
|
||||
auto ec = esr >> 26;
|
||||
auto iss = esr & 0xFFFFFF;
|
||||
|
||||
switch (ec) {
|
||||
case 0x15: // SVC
|
||||
Kernel::CallSVC(iss);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int size, u64 value,
|
||||
void* user_data) {
|
||||
ARM_Interface::ThreadContext ctx{};
|
||||
@@ -57,7 +72,7 @@ static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int si
|
||||
return {};
|
||||
}
|
||||
|
||||
ARM_Unicorn::ARM_Unicorn(System& system) : system{system} {
|
||||
ARM_Unicorn::ARM_Unicorn(Timing::CoreTiming& core_timing) : core_timing{core_timing} {
|
||||
CHECKED(uc_open(UC_ARCH_ARM64, UC_MODE_ARM, &uc));
|
||||
|
||||
auto fpv = 3 << 20;
|
||||
@@ -162,7 +177,7 @@ void ARM_Unicorn::Run() {
|
||||
if (GDBStub::IsServerEnabled()) {
|
||||
ExecuteInstructions(std::max(4000000, 0));
|
||||
} else {
|
||||
ExecuteInstructions(std::max(system.CoreTiming().GetDowncount(), 0));
|
||||
ExecuteInstructions(std::max(core_timing.GetDowncount(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +190,7 @@ MICROPROFILE_DEFINE(ARM_Jit_Unicorn, "ARM JIT", "Unicorn", MP_RGB(255, 64, 64));
|
||||
void ARM_Unicorn::ExecuteInstructions(int num_instructions) {
|
||||
MICROPROFILE_SCOPE(ARM_Jit_Unicorn);
|
||||
CHECKED(uc_emu_start(uc, GetPC(), 1ULL << 63, 0, num_instructions));
|
||||
system.CoreTiming().AddTicks(num_instructions);
|
||||
core_timing.AddTicks(num_instructions);
|
||||
if (GDBStub::IsServerEnabled()) {
|
||||
if (last_bkpt_hit && last_bkpt.type == GDBStub::BreakpointType::Execute) {
|
||||
uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address);
|
||||
@@ -258,20 +273,4 @@ void ARM_Unicorn::RecordBreak(GDBStub::BreakpointAddress bkpt) {
|
||||
last_bkpt_hit = true;
|
||||
}
|
||||
|
||||
void ARM_Unicorn::InterruptHook(uc_engine* uc, u32 int_no, void* user_data) {
|
||||
u32 esr{};
|
||||
CHECKED(uc_reg_read(uc, UC_ARM64_REG_ESR, &esr));
|
||||
|
||||
const auto ec = esr >> 26;
|
||||
const auto iss = esr & 0xFFFFFF;
|
||||
|
||||
auto* const arm_instance = static_cast<ARM_Unicorn*>(user_data);
|
||||
|
||||
switch (ec) {
|
||||
case 0x15: // SVC
|
||||
Kernel::CallSVC(arm_instance->system, iss);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -9,13 +9,15 @@
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
|
||||
namespace Core {
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
}
|
||||
|
||||
class System;
|
||||
namespace Core {
|
||||
|
||||
class ARM_Unicorn final : public ARM_Interface {
|
||||
public:
|
||||
explicit ARM_Unicorn(System& system);
|
||||
explicit ARM_Unicorn(Timing::CoreTiming& core_timing);
|
||||
~ARM_Unicorn() override;
|
||||
|
||||
void MapBackingMemory(VAddr address, std::size_t size, u8* memory,
|
||||
@@ -45,10 +47,8 @@ public:
|
||||
void RecordBreak(GDBStub::BreakpointAddress bkpt);
|
||||
|
||||
private:
|
||||
static void InterruptHook(uc_engine* uc, u32 int_no, void* user_data);
|
||||
|
||||
uc_engine* uc{};
|
||||
System& system;
|
||||
Timing::CoreTiming& core_timing;
|
||||
GDBStub::BreakpointAddress last_bkpt{};
|
||||
bool last_bkpt_hit = false;
|
||||
};
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "core/core_timing.h"
|
||||
#include "core/cpu_core_manager.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/vfs_concat.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
@@ -109,8 +108,6 @@ struct System::Impl {
|
||||
// Create a default fs if one doesn't already exist.
|
||||
if (virtual_filesystem == nullptr)
|
||||
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
|
||||
if (content_provider == nullptr)
|
||||
content_provider = std::make_unique<FileSys::ContentProviderUnion>();
|
||||
|
||||
/// Create default implementations of applets if one is not provided.
|
||||
if (profile_selector == nullptr)
|
||||
@@ -252,8 +249,6 @@ struct System::Impl {
|
||||
Kernel::KernelCore kernel;
|
||||
/// RealVfsFilesystem instance
|
||||
FileSys::VirtualFilesystem virtual_filesystem;
|
||||
/// ContentProviderUnion instance
|
||||
std::unique_ptr<FileSys::ContentProviderUnion> content_provider;
|
||||
/// AppLoader used to load the current executing application
|
||||
std::unique_ptr<Loader::AppLoader> app_loader;
|
||||
std::unique_ptr<VideoCore::RendererBase> renderer;
|
||||
@@ -493,27 +488,6 @@ const Frontend::SoftwareKeyboardApplet& System::GetSoftwareKeyboard() const {
|
||||
return *impl->software_keyboard;
|
||||
}
|
||||
|
||||
void System::SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider) {
|
||||
impl->content_provider = std::move(provider);
|
||||
}
|
||||
|
||||
FileSys::ContentProvider& System::GetContentProvider() {
|
||||
return *impl->content_provider;
|
||||
}
|
||||
|
||||
const FileSys::ContentProvider& System::GetContentProvider() const {
|
||||
return *impl->content_provider;
|
||||
}
|
||||
|
||||
void System::RegisterContentProvider(FileSys::ContentProviderUnionSlot slot,
|
||||
FileSys::ContentProvider* provider) {
|
||||
impl->content_provider->SetSlot(slot, provider);
|
||||
}
|
||||
|
||||
void System::ClearContentProvider(FileSys::ContentProviderUnionSlot slot) {
|
||||
impl->content_provider->ClearSlot(slot);
|
||||
}
|
||||
|
||||
void System::SetWebBrowser(std::unique_ptr<Frontend::WebBrowserApplet> applet) {
|
||||
impl->web_browser = std::move(applet);
|
||||
}
|
||||
|
||||
@@ -21,9 +21,6 @@ class WebBrowserApplet;
|
||||
|
||||
namespace FileSys {
|
||||
class CheatList;
|
||||
class ContentProvider;
|
||||
class ContentProviderUnion;
|
||||
enum class ContentProviderUnionSlot;
|
||||
class VfsFilesystem;
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -273,17 +270,6 @@ public:
|
||||
Frontend::WebBrowserApplet& GetWebBrowser();
|
||||
const Frontend::WebBrowserApplet& GetWebBrowser() const;
|
||||
|
||||
void SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider);
|
||||
|
||||
FileSys::ContentProvider& GetContentProvider();
|
||||
|
||||
const FileSys::ContentProvider& GetContentProvider() const;
|
||||
|
||||
void RegisterContentProvider(FileSys::ContentProviderUnionSlot slot,
|
||||
FileSys::ContentProvider* provider);
|
||||
|
||||
void ClearContentProvider(FileSys::ContentProviderUnionSlot slot);
|
||||
|
||||
private:
|
||||
System();
|
||||
|
||||
|
||||
@@ -55,13 +55,13 @@ Cpu::Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_ba
|
||||
: cpu_barrier{cpu_barrier}, core_timing{system.CoreTiming()}, core_index{core_index} {
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
arm_interface = std::make_unique<ARM_Dynarmic>(system, exclusive_monitor, core_index);
|
||||
arm_interface = std::make_unique<ARM_Dynarmic>(core_timing, exclusive_monitor, core_index);
|
||||
#else
|
||||
arm_interface = std::make_unique<ARM_Unicorn>(system);
|
||||
arm_interface = std::make_unique<ARM_Unicorn>();
|
||||
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
|
||||
#endif
|
||||
} else {
|
||||
arm_interface = std::make_unique<ARM_Unicorn>(system);
|
||||
arm_interface = std::make_unique<ARM_Unicorn>(core_timing);
|
||||
}
|
||||
|
||||
scheduler = std::make_unique<Kernel::Scheduler>(system, *arm_interface);
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include "common/file_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/crypto/partition_data_manager.h"
|
||||
@@ -795,7 +794,7 @@ void KeyManager::DeriveBase() {
|
||||
|
||||
void KeyManager::DeriveETicket(PartitionDataManager& data) {
|
||||
// ETicket keys
|
||||
const auto es = Core::System::GetInstance().GetContentProvider().GetEntry(
|
||||
const auto es = Service::FileSystem::GetUnionContents().GetEntry(
|
||||
0x0100000000000033, FileSys::ContentRecordType::Program);
|
||||
|
||||
if (es == nullptr)
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "common/file_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/ips_layer.h"
|
||||
@@ -70,7 +69,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
}
|
||||
}
|
||||
|
||||
const auto& installed = Core::System::GetInstance().GetContentProvider();
|
||||
const auto installed = Service::FileSystem::GetUnionContents();
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
const auto update_disabled =
|
||||
@@ -156,7 +155,7 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::string& name) const {
|
||||
std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
|
||||
if (nso.size() < sizeof(Loader::NSOHeader)) {
|
||||
return nso;
|
||||
}
|
||||
@@ -172,19 +171,18 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
|
||||
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
|
||||
|
||||
if (Settings::values.dump_nso) {
|
||||
LOG_INFO(Loader, "Dumping NSO for name={}, build_id={}, title_id={:016X}", name, build_id,
|
||||
title_id);
|
||||
LOG_INFO(Loader, "Dumping NSO for build_id={}, title_id={:016X}", build_id, title_id);
|
||||
const auto dump_dir = Service::FileSystem::GetModificationDumpRoot(title_id);
|
||||
if (dump_dir != nullptr) {
|
||||
const auto nso_dir = GetOrCreateDirectoryRelative(dump_dir, "/nso");
|
||||
const auto file = nso_dir->CreateFile(fmt::format("{}-{}.nso", name, build_id));
|
||||
const auto file = nso_dir->CreateFile(fmt::format("{}.nso", build_id));
|
||||
|
||||
file->Resize(nso.size());
|
||||
file->WriteBytes(nso);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id);
|
||||
LOG_INFO(Loader, "Patching NSO for build_id={}", build_id);
|
||||
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
@@ -347,7 +345,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
|
||||
if (romfs == nullptr)
|
||||
return romfs;
|
||||
|
||||
const auto& installed = Core::System::GetInstance().GetContentProvider();
|
||||
const auto installed = Service::FileSystem::GetUnionContents();
|
||||
|
||||
// Game Updates
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
@@ -394,7 +392,7 @@ static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
|
||||
std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
|
||||
VirtualFile update_raw) const {
|
||||
std::map<std::string, std::string, std::less<>> out;
|
||||
const auto& installed = Core::System::GetInstance().GetContentProvider();
|
||||
const auto installed = Service::FileSystem::GetUnionContents();
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
|
||||
// Game Updates
|
||||
@@ -468,10 +466,10 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
|
||||
|
||||
// DLC
|
||||
const auto dlc_entries = installed.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
|
||||
std::vector<ContentProviderEntry> dlc_match;
|
||||
std::vector<RegisteredCacheEntry> dlc_match;
|
||||
dlc_match.reserve(dlc_entries.size());
|
||||
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
|
||||
[this, &installed](const ContentProviderEntry& entry) {
|
||||
[this, &installed](const RegisteredCacheEntry& entry) {
|
||||
return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == title_id &&
|
||||
installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;
|
||||
});
|
||||
@@ -494,7 +492,7 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
|
||||
}
|
||||
|
||||
std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
|
||||
const auto& installed = Core::System::GetInstance().GetContentProvider();
|
||||
const auto installed{Service::FileSystem::GetUnionContents()};
|
||||
|
||||
const auto base_control_nca = installed.GetEntry(title_id, ContentRecordType::Control);
|
||||
if (base_control_nca == nullptr)
|
||||
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
// Currently tracked NSO patches:
|
||||
// - IPS
|
||||
// - IPSwitch
|
||||
std::vector<u8> PatchNSO(const std::vector<u8>& nso, const std::string& name) const;
|
||||
std::vector<u8> PatchNSO(const std::vector<u8>& nso) const;
|
||||
|
||||
// Checks to see if PatchNSO() will have any effect given the NSO's build ID.
|
||||
// Used to prevent expensive copies in NSO loader.
|
||||
|
||||
@@ -23,19 +23,19 @@ namespace FileSys {
|
||||
// The size of blocks to use when vfs raw copying into nand.
|
||||
constexpr size_t VFS_RC_LARGE_COPY_BLOCK = 0x400000;
|
||||
|
||||
std::string ContentProviderEntry::DebugInfo() const {
|
||||
std::string RegisteredCacheEntry::DebugInfo() const {
|
||||
return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type));
|
||||
}
|
||||
|
||||
bool operator<(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs) {
|
||||
bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
|
||||
return (lhs.title_id < rhs.title_id) || (lhs.title_id == rhs.title_id && lhs.type < rhs.type);
|
||||
}
|
||||
|
||||
bool operator==(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs) {
|
||||
bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
|
||||
return std::tie(lhs.title_id, lhs.type) == std::tie(rhs.title_id, rhs.type);
|
||||
}
|
||||
|
||||
bool operator!=(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs) {
|
||||
bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
|
||||
return !operator==(lhs, rhs);
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ static std::string GetCNMTName(TitleType type, u64 title_id) {
|
||||
return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id);
|
||||
}
|
||||
|
||||
ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
|
||||
static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
|
||||
switch (type) {
|
||||
case NCAContentType::Program:
|
||||
// TODO(DarkLordZach): Differentiate between Program and Patch
|
||||
@@ -104,28 +104,6 @@ ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
|
||||
}
|
||||
}
|
||||
|
||||
ContentProvider::~ContentProvider() = default;
|
||||
|
||||
bool ContentProvider::HasEntry(ContentProviderEntry entry) const {
|
||||
return HasEntry(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
VirtualFile ContentProvider::GetEntryUnparsed(ContentProviderEntry entry) const {
|
||||
return GetEntryUnparsed(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
VirtualFile ContentProvider::GetEntryRaw(ContentProviderEntry entry) const {
|
||||
return GetEntryRaw(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
std::unique_ptr<NCA> ContentProvider::GetEntry(ContentProviderEntry entry) const {
|
||||
return GetEntry(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
std::vector<ContentProviderEntry> ContentProvider::ListEntries() const {
|
||||
return ListEntriesFilter(std::nullopt, std::nullopt, std::nullopt);
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
|
||||
std::string_view path) const {
|
||||
const auto file = dir->GetFileRelative(path);
|
||||
@@ -183,8 +161,8 @@ VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
|
||||
return file;
|
||||
}
|
||||
|
||||
static std::optional<NcaID> CheckMapForContentRecord(const std::map<u64, CNMT>& map, u64 title_id,
|
||||
ContentRecordType type) {
|
||||
static std::optional<NcaID> CheckMapForContentRecord(
|
||||
const boost::container::flat_map<u64, CNMT>& map, u64 title_id, ContentRecordType type) {
|
||||
if (map.find(title_id) == map.end())
|
||||
return {};
|
||||
|
||||
@@ -290,7 +268,7 @@ void RegisteredCache::Refresh() {
|
||||
AccumulateYuzuMeta();
|
||||
}
|
||||
|
||||
RegisteredCache::RegisteredCache(VirtualDir dir_, ContentProviderParsingFunction parsing_function)
|
||||
RegisteredCache::RegisteredCache(VirtualDir dir_, RegisteredCacheParsingFunction parsing_function)
|
||||
: dir(std::move(dir_)), parser(std::move(parsing_function)) {
|
||||
Refresh();
|
||||
}
|
||||
@@ -301,11 +279,19 @@ bool RegisteredCache::HasEntry(u64 title_id, ContentRecordType type) const {
|
||||
return GetEntryRaw(title_id, type) != nullptr;
|
||||
}
|
||||
|
||||
bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const {
|
||||
return GetEntryRaw(entry) != nullptr;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
|
||||
const auto id = GetNcaIDFromMetadata(title_id, type);
|
||||
return id ? GetFileAtID(*id) : nullptr;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::GetEntryUnparsed(RegisteredCacheEntry entry) const {
|
||||
return GetEntryUnparsed(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
std::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
|
||||
const auto meta_iter = meta.find(title_id);
|
||||
if (meta_iter != meta.end())
|
||||
@@ -323,6 +309,10 @@ VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) c
|
||||
return id ? parser(GetFileAtID(*id), *id) : nullptr;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const {
|
||||
return GetEntryRaw(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const {
|
||||
const auto raw = GetEntryRaw(title_id, type);
|
||||
if (raw == nullptr)
|
||||
@@ -330,6 +320,10 @@ std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType t
|
||||
return std::make_unique<NCA>(raw, nullptr, 0, keys);
|
||||
}
|
||||
|
||||
std::unique_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
|
||||
return GetEntry(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void RegisteredCache::IterateAllMetadata(
|
||||
std::vector<T>& out, std::function<T(const CNMT&, const ContentRecord&)> proc,
|
||||
@@ -354,14 +348,25 @@ void RegisteredCache::IterateAllMetadata(
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ContentProviderEntry> RegisteredCache::ListEntriesFilter(
|
||||
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
|
||||
std::optional<u64> title_id) const {
|
||||
std::vector<ContentProviderEntry> out;
|
||||
IterateAllMetadata<ContentProviderEntry>(
|
||||
std::vector<RegisteredCacheEntry> RegisteredCache::ListEntries() const {
|
||||
std::vector<RegisteredCacheEntry> out;
|
||||
IterateAllMetadata<RegisteredCacheEntry>(
|
||||
out,
|
||||
[](const CNMT& c, const ContentRecord& r) {
|
||||
return ContentProviderEntry{c.GetTitleID(), r.type};
|
||||
return RegisteredCacheEntry{c.GetTitleID(), r.type};
|
||||
},
|
||||
[](const CNMT& c, const ContentRecord& r) { return true; });
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
|
||||
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
|
||||
std::optional<u64> title_id) const {
|
||||
std::vector<RegisteredCacheEntry> out;
|
||||
IterateAllMetadata<RegisteredCacheEntry>(
|
||||
out,
|
||||
[](const CNMT& c, const ContentRecord& r) {
|
||||
return RegisteredCacheEntry{c.GetTitleID(), r.type};
|
||||
},
|
||||
[&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
|
||||
if (title_type && *title_type != c.GetType())
|
||||
@@ -516,56 +521,37 @@ bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
|
||||
}) != yuzu_meta.end();
|
||||
}
|
||||
|
||||
ContentProviderUnion::~ContentProviderUnion() = default;
|
||||
RegisteredCacheUnion::RegisteredCacheUnion(std::vector<RegisteredCache*> caches)
|
||||
: caches(std::move(caches)) {}
|
||||
|
||||
void ContentProviderUnion::SetSlot(ContentProviderUnionSlot slot, ContentProvider* provider) {
|
||||
providers[slot] = provider;
|
||||
void RegisteredCacheUnion::Refresh() {
|
||||
for (const auto& c : caches)
|
||||
c->Refresh();
|
||||
}
|
||||
|
||||
void ContentProviderUnion::ClearSlot(ContentProviderUnionSlot slot) {
|
||||
providers[slot] = nullptr;
|
||||
bool RegisteredCacheUnion::HasEntry(u64 title_id, ContentRecordType type) const {
|
||||
return std::any_of(caches.begin(), caches.end(), [title_id, type](const auto& cache) {
|
||||
return cache->HasEntry(title_id, type);
|
||||
});
|
||||
}
|
||||
|
||||
void ContentProviderUnion::Refresh() {
|
||||
for (auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
|
||||
provider.second->Refresh();
|
||||
}
|
||||
bool RegisteredCacheUnion::HasEntry(RegisteredCacheEntry entry) const {
|
||||
return HasEntry(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
bool ContentProviderUnion::HasEntry(u64 title_id, ContentRecordType type) const {
|
||||
for (const auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
|
||||
if (provider.second->HasEntry(title_id, type))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<u32> ContentProviderUnion::GetEntryVersion(u64 title_id) const {
|
||||
for (const auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
|
||||
const auto res = provider.second->GetEntryVersion(title_id);
|
||||
if (res != std::nullopt)
|
||||
std::optional<u32> RegisteredCacheUnion::GetEntryVersion(u64 title_id) const {
|
||||
for (const auto& c : caches) {
|
||||
const auto res = c->GetEntryVersion(title_id);
|
||||
if (res)
|
||||
return res;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
return {};
|
||||
}
|
||||
|
||||
VirtualFile ContentProviderUnion::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
|
||||
for (const auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
|
||||
const auto res = provider.second->GetEntryUnparsed(title_id, type);
|
||||
VirtualFile RegisteredCacheUnion::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
|
||||
for (const auto& c : caches) {
|
||||
const auto res = c->GetEntryUnparsed(title_id, type);
|
||||
if (res != nullptr)
|
||||
return res;
|
||||
}
|
||||
@@ -573,12 +559,13 @@ VirtualFile ContentProviderUnion::GetEntryUnparsed(u64 title_id, ContentRecordTy
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
VirtualFile ContentProviderUnion::GetEntryRaw(u64 title_id, ContentRecordType type) const {
|
||||
for (const auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
VirtualFile RegisteredCacheUnion::GetEntryUnparsed(RegisteredCacheEntry entry) const {
|
||||
return GetEntryUnparsed(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
const auto res = provider.second->GetEntryRaw(title_id, type);
|
||||
VirtualFile RegisteredCacheUnion::GetEntryRaw(u64 title_id, ContentRecordType type) const {
|
||||
for (const auto& c : caches) {
|
||||
const auto res = c->GetEntryRaw(title_id, type);
|
||||
if (res != nullptr)
|
||||
return res;
|
||||
}
|
||||
@@ -586,30 +573,30 @@ VirtualFile ContentProviderUnion::GetEntryRaw(u64 title_id, ContentRecordType ty
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<NCA> ContentProviderUnion::GetEntry(u64 title_id, ContentRecordType type) const {
|
||||
for (const auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
|
||||
auto res = provider.second->GetEntry(title_id, type);
|
||||
if (res != nullptr)
|
||||
return res;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
VirtualFile RegisteredCacheUnion::GetEntryRaw(RegisteredCacheEntry entry) const {
|
||||
return GetEntryRaw(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
std::vector<ContentProviderEntry> ContentProviderUnion::ListEntriesFilter(
|
||||
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
|
||||
std::optional<u64> title_id) const {
|
||||
std::vector<ContentProviderEntry> out;
|
||||
std::unique_ptr<NCA> RegisteredCacheUnion::GetEntry(u64 title_id, ContentRecordType type) const {
|
||||
const auto raw = GetEntryRaw(title_id, type);
|
||||
if (raw == nullptr)
|
||||
return nullptr;
|
||||
return std::make_unique<NCA>(raw);
|
||||
}
|
||||
|
||||
for (const auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
std::unique_ptr<NCA> RegisteredCacheUnion::GetEntry(RegisteredCacheEntry entry) const {
|
||||
return GetEntry(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
const auto vec = provider.second->ListEntriesFilter(title_type, record_type, title_id);
|
||||
std::copy(vec.begin(), vec.end(), std::back_inserter(out));
|
||||
std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntries() const {
|
||||
std::vector<RegisteredCacheEntry> out;
|
||||
for (const auto& c : caches) {
|
||||
c->IterateAllMetadata<RegisteredCacheEntry>(
|
||||
out,
|
||||
[](const CNMT& c, const ContentRecord& r) {
|
||||
return RegisteredCacheEntry{c.GetTitleID(), r.type};
|
||||
},
|
||||
[](const CNMT& c, const ContentRecord& r) { return true; });
|
||||
}
|
||||
|
||||
std::sort(out.begin(), out.end());
|
||||
@@ -617,87 +604,25 @@ std::vector<ContentProviderEntry> ContentProviderUnion::ListEntriesFilter(
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>>
|
||||
ContentProviderUnion::ListEntriesFilterOrigin(std::optional<ContentProviderUnionSlot> origin,
|
||||
std::optional<TitleType> title_type,
|
||||
std::optional<ContentRecordType> record_type,
|
||||
std::optional<u64> title_id) const {
|
||||
std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> out;
|
||||
|
||||
for (const auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
|
||||
if (origin.has_value() && *origin != provider.first)
|
||||
continue;
|
||||
|
||||
const auto vec = provider.second->ListEntriesFilter(title_type, record_type, title_id);
|
||||
std::transform(vec.begin(), vec.end(), std::back_inserter(out),
|
||||
[&provider](const ContentProviderEntry& entry) {
|
||||
return std::make_pair(provider.first, entry);
|
||||
});
|
||||
}
|
||||
|
||||
std::sort(out.begin(), out.end());
|
||||
out.erase(std::unique(out.begin(), out.end()), out.end());
|
||||
return out;
|
||||
}
|
||||
|
||||
ManualContentProvider::~ManualContentProvider() = default;
|
||||
|
||||
void ManualContentProvider::AddEntry(TitleType title_type, ContentRecordType content_type,
|
||||
u64 title_id, VirtualFile file) {
|
||||
entries.insert_or_assign({title_type, content_type, title_id}, file);
|
||||
}
|
||||
|
||||
void ManualContentProvider::ClearAllEntries() {
|
||||
entries.clear();
|
||||
}
|
||||
|
||||
void ManualContentProvider::Refresh() {}
|
||||
|
||||
bool ManualContentProvider::HasEntry(u64 title_id, ContentRecordType type) const {
|
||||
return GetEntryRaw(title_id, type) != nullptr;
|
||||
}
|
||||
|
||||
std::optional<u32> ManualContentProvider::GetEntryVersion(u64 title_id) const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
VirtualFile ManualContentProvider::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
|
||||
return GetEntryRaw(title_id, type);
|
||||
}
|
||||
|
||||
VirtualFile ManualContentProvider::GetEntryRaw(u64 title_id, ContentRecordType type) const {
|
||||
const auto iter =
|
||||
std::find_if(entries.begin(), entries.end(), [title_id, type](const auto& entry) {
|
||||
const auto [title_type, content_type, e_title_id] = entry.first;
|
||||
return content_type == type && e_title_id == title_id;
|
||||
});
|
||||
if (iter == entries.end())
|
||||
return nullptr;
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
std::unique_ptr<NCA> ManualContentProvider::GetEntry(u64 title_id, ContentRecordType type) const {
|
||||
const auto res = GetEntryRaw(title_id, type);
|
||||
if (res == nullptr)
|
||||
return nullptr;
|
||||
return std::make_unique<NCA>(res, nullptr, 0, keys);
|
||||
}
|
||||
|
||||
std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter(
|
||||
std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntriesFilter(
|
||||
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
|
||||
std::optional<u64> title_id) const {
|
||||
std::vector<ContentProviderEntry> out;
|
||||
|
||||
for (const auto& entry : entries) {
|
||||
const auto [e_title_type, e_content_type, e_title_id] = entry.first;
|
||||
if ((title_type == std::nullopt || e_title_type == *title_type) &&
|
||||
(record_type == std::nullopt || e_content_type == *record_type) &&
|
||||
(title_id == std::nullopt || e_title_id == *title_id)) {
|
||||
out.emplace_back(ContentProviderEntry{e_title_id, e_content_type});
|
||||
}
|
||||
std::vector<RegisteredCacheEntry> out;
|
||||
for (const auto& c : caches) {
|
||||
c->IterateAllMetadata<RegisteredCacheEntry>(
|
||||
out,
|
||||
[](const CNMT& c, const ContentRecord& r) {
|
||||
return RegisteredCacheEntry{c.GetTitleID(), r.type};
|
||||
},
|
||||
[&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
|
||||
if (title_type && *title_type != c.GetType())
|
||||
return false;
|
||||
if (record_type && *record_type != r.type)
|
||||
return false;
|
||||
if (title_id && *title_id != c.GetTitleID())
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
std::sort(out.begin(), out.end());
|
||||
|
||||
@@ -21,13 +21,12 @@ class NSP;
|
||||
class XCI;
|
||||
|
||||
enum class ContentRecordType : u8;
|
||||
enum class NCAContentType : u8;
|
||||
enum class TitleType : u8;
|
||||
|
||||
struct ContentRecord;
|
||||
|
||||
using NcaID = std::array<u8, 0x10>;
|
||||
using ContentProviderParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
|
||||
using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
|
||||
using VfsCopyFunction = std::function<bool(const VirtualFile&, const VirtualFile&, size_t)>;
|
||||
|
||||
enum class InstallResult {
|
||||
@@ -37,7 +36,7 @@ enum class InstallResult {
|
||||
ErrorMetaFailed,
|
||||
};
|
||||
|
||||
struct ContentProviderEntry {
|
||||
struct RegisteredCacheEntry {
|
||||
u64 title_id;
|
||||
ContentRecordType type;
|
||||
|
||||
@@ -48,46 +47,12 @@ constexpr u64 GetUpdateTitleID(u64 base_title_id) {
|
||||
return base_title_id | 0x800;
|
||||
}
|
||||
|
||||
ContentRecordType GetCRTypeFromNCAType(NCAContentType type);
|
||||
|
||||
// boost flat_map requires operator< for O(log(n)) lookups.
|
||||
bool operator<(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs);
|
||||
bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
|
||||
|
||||
// std unique requires operator== to identify duplicates.
|
||||
bool operator==(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs);
|
||||
bool operator!=(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs);
|
||||
|
||||
class ContentProvider {
|
||||
public:
|
||||
virtual ~ContentProvider();
|
||||
|
||||
virtual void Refresh() = 0;
|
||||
|
||||
virtual bool HasEntry(u64 title_id, ContentRecordType type) const = 0;
|
||||
virtual bool HasEntry(ContentProviderEntry entry) const;
|
||||
|
||||
virtual std::optional<u32> GetEntryVersion(u64 title_id) const = 0;
|
||||
|
||||
virtual VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const = 0;
|
||||
virtual VirtualFile GetEntryUnparsed(ContentProviderEntry entry) const;
|
||||
|
||||
virtual VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const = 0;
|
||||
virtual VirtualFile GetEntryRaw(ContentProviderEntry entry) const;
|
||||
|
||||
virtual std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const = 0;
|
||||
virtual std::unique_ptr<NCA> GetEntry(ContentProviderEntry entry) const;
|
||||
|
||||
virtual std::vector<ContentProviderEntry> ListEntries() const;
|
||||
|
||||
// If a parameter is not std::nullopt, it will be filtered for from all entries.
|
||||
virtual std::vector<ContentProviderEntry> ListEntriesFilter(
|
||||
std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
|
||||
std::optional<u64> title_id = {}) const = 0;
|
||||
|
||||
protected:
|
||||
// A single instance of KeyManager to be used by GetEntry()
|
||||
Core::Crypto::KeyManager keys;
|
||||
};
|
||||
bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
|
||||
bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
|
||||
|
||||
/*
|
||||
* A class that catalogues NCAs in the registered directory structure.
|
||||
@@ -102,32 +67,39 @@ protected:
|
||||
* (This impl also supports substituting the nca dir for an nca file, as that's more convenient
|
||||
* when 4GB splitting can be ignored.)
|
||||
*/
|
||||
class RegisteredCache : public ContentProvider {
|
||||
class RegisteredCache {
|
||||
friend class RegisteredCacheUnion;
|
||||
|
||||
public:
|
||||
// Parsing function defines the conversion from raw file to NCA. If there are other steps
|
||||
// besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom
|
||||
// parsing function.
|
||||
explicit RegisteredCache(VirtualDir dir,
|
||||
ContentProviderParsingFunction parsing_function =
|
||||
RegisteredCacheParsingFunction parsing_function =
|
||||
[](const VirtualFile& file, const NcaID& id) { return file; });
|
||||
~RegisteredCache() override;
|
||||
~RegisteredCache();
|
||||
|
||||
void Refresh() override;
|
||||
void Refresh();
|
||||
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const override;
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const;
|
||||
bool HasEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::optional<u32> GetEntryVersion(u64 title_id) const override;
|
||||
std::optional<u32> GetEntryVersion(u64 title_id) const;
|
||||
|
||||
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const override;
|
||||
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
|
||||
|
||||
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const override;
|
||||
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const override;
|
||||
std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
|
||||
std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::vector<RegisteredCacheEntry> ListEntries() const;
|
||||
// If a parameter is not std::nullopt, it will be filtered for from all entries.
|
||||
std::vector<ContentProviderEntry> ListEntriesFilter(
|
||||
std::vector<RegisteredCacheEntry> ListEntriesFilter(
|
||||
std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
|
||||
std::optional<u64> title_id = {}) const override;
|
||||
std::optional<u64> title_id = {}) const;
|
||||
|
||||
// Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure
|
||||
// there is a meta NCA and all of them are accessible.
|
||||
@@ -159,70 +131,46 @@ private:
|
||||
bool RawInstallYuzuMeta(const CNMT& cnmt);
|
||||
|
||||
VirtualDir dir;
|
||||
ContentProviderParsingFunction parser;
|
||||
RegisteredCacheParsingFunction parser;
|
||||
Core::Crypto::KeyManager keys;
|
||||
|
||||
// maps tid -> NcaID of meta
|
||||
std::map<u64, NcaID> meta_id;
|
||||
boost::container::flat_map<u64, NcaID> meta_id;
|
||||
// maps tid -> meta
|
||||
std::map<u64, CNMT> meta;
|
||||
boost::container::flat_map<u64, CNMT> meta;
|
||||
// maps tid -> meta for CNMT in yuzu_meta
|
||||
std::map<u64, CNMT> yuzu_meta;
|
||||
boost::container::flat_map<u64, CNMT> yuzu_meta;
|
||||
};
|
||||
|
||||
enum class ContentProviderUnionSlot {
|
||||
SysNAND, ///< System NAND
|
||||
UserNAND, ///< User NAND
|
||||
SDMC, ///< SD Card
|
||||
FrontendManual, ///< Frontend-defined game list or similar
|
||||
};
|
||||
|
||||
// Combines multiple ContentProvider(s) (i.e. SysNAND, UserNAND, SDMC) into one interface.
|
||||
class ContentProviderUnion : public ContentProvider {
|
||||
// Combines multiple RegisteredCaches (i.e. SysNAND, UserNAND, SDMC) into one interface.
|
||||
class RegisteredCacheUnion {
|
||||
public:
|
||||
~ContentProviderUnion() override;
|
||||
explicit RegisteredCacheUnion(std::vector<RegisteredCache*> caches);
|
||||
|
||||
void SetSlot(ContentProviderUnionSlot slot, ContentProvider* provider);
|
||||
void ClearSlot(ContentProviderUnionSlot slot);
|
||||
void Refresh();
|
||||
|
||||
void Refresh() override;
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const override;
|
||||
std::optional<u32> GetEntryVersion(u64 title_id) const override;
|
||||
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const override;
|
||||
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const override;
|
||||
std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const override;
|
||||
std::vector<ContentProviderEntry> ListEntriesFilter(
|
||||
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
|
||||
std::optional<u64> title_id) const override;
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const;
|
||||
bool HasEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> ListEntriesFilterOrigin(
|
||||
std::optional<ContentProviderUnionSlot> origin = {},
|
||||
std::optional<u32> GetEntryVersion(u64 title_id) const;
|
||||
|
||||
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
|
||||
|
||||
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
|
||||
std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::vector<RegisteredCacheEntry> ListEntries() const;
|
||||
// If a parameter is not std::nullopt, it will be filtered for from all entries.
|
||||
std::vector<RegisteredCacheEntry> ListEntriesFilter(
|
||||
std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
|
||||
std::optional<u64> title_id = {}) const;
|
||||
|
||||
private:
|
||||
std::map<ContentProviderUnionSlot, ContentProvider*> providers;
|
||||
};
|
||||
|
||||
class ManualContentProvider : public ContentProvider {
|
||||
public:
|
||||
~ManualContentProvider() override;
|
||||
|
||||
void AddEntry(TitleType title_type, ContentRecordType content_type, u64 title_id,
|
||||
VirtualFile file);
|
||||
void ClearAllEntries();
|
||||
|
||||
void Refresh() override;
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const override;
|
||||
std::optional<u32> GetEntryVersion(u64 title_id) const override;
|
||||
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const override;
|
||||
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const override;
|
||||
std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const override;
|
||||
std::vector<ContentProviderEntry> ListEntriesFilter(
|
||||
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
|
||||
std::optional<u64> title_id) const override;
|
||||
|
||||
private:
|
||||
std::map<std::tuple<TitleType, ContentRecordType, u64>, VirtualFile> entries;
|
||||
std::vector<RegisteredCache*> caches;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -48,7 +48,7 @@ ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, Conte
|
||||
|
||||
switch (storage) {
|
||||
case StorageId::None:
|
||||
res = Core::System::GetInstance().GetContentProvider().GetEntry(title_id, type);
|
||||
res = Service::FileSystem::GetUnionContents().GetEntry(title_id, type);
|
||||
break;
|
||||
case StorageId::NandSystem:
|
||||
res = Service::FileSystem::GetSystemNANDContents()->GetEntry(title_id, type);
|
||||
|
||||
@@ -143,12 +143,11 @@ std::multimap<u64, std::shared_ptr<NCA>> NSP::GetNCAsByTitleID() const {
|
||||
return out;
|
||||
}
|
||||
|
||||
std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>>
|
||||
NSP::GetNCAs() const {
|
||||
std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> NSP::GetNCAs() const {
|
||||
return ncas;
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type, TitleType title_type) const {
|
||||
std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type) const {
|
||||
if (extracted)
|
||||
LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
|
||||
|
||||
@@ -156,14 +155,14 @@ std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type, TitleType
|
||||
if (title_id_iter == ncas.end())
|
||||
return nullptr;
|
||||
|
||||
const auto type_iter = title_id_iter->second.find({title_type, type});
|
||||
const auto type_iter = title_id_iter->second.find(type);
|
||||
if (type_iter == title_id_iter->second.end())
|
||||
return nullptr;
|
||||
|
||||
return type_iter->second;
|
||||
}
|
||||
|
||||
VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type, TitleType title_type) const {
|
||||
VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type) const {
|
||||
if (extracted)
|
||||
LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
|
||||
const auto nca = GetNCA(title_id, type);
|
||||
@@ -241,7 +240,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
|
||||
const CNMT cnmt(inner_file);
|
||||
auto& ncas_title = ncas[cnmt.GetTitleID()];
|
||||
|
||||
ncas_title[{cnmt.GetType(), ContentRecordType::Meta}] = nca;
|
||||
ncas_title[ContentRecordType::Meta] = nca;
|
||||
for (const auto& rec : cnmt.GetContentRecords()) {
|
||||
const auto id_string = Common::HexArrayToString(rec.nca_id, false);
|
||||
const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
|
||||
@@ -259,7 +258,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
|
||||
if (next_nca->GetStatus() == Loader::ResultStatus::Success ||
|
||||
(next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
|
||||
(cnmt.GetTitleID() & 0x800) != 0)) {
|
||||
ncas_title[{cnmt.GetType(), rec.type}] = std::move(next_nca);
|
||||
ncas_title[rec.type] = std::move(next_nca);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,12 +42,9 @@ public:
|
||||
// Type 0 Only (Collection of NCAs + Certificate + Ticket + Meta XML)
|
||||
std::vector<std::shared_ptr<NCA>> GetNCAsCollapsed() const;
|
||||
std::multimap<u64, std::shared_ptr<NCA>> GetNCAsByTitleID() const;
|
||||
std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> GetNCAs()
|
||||
const;
|
||||
std::shared_ptr<NCA> GetNCA(u64 title_id, ContentRecordType type,
|
||||
TitleType title_type = TitleType::Application) const;
|
||||
VirtualFile GetNCAFile(u64 title_id, ContentRecordType type,
|
||||
TitleType title_type = TitleType::Application) const;
|
||||
std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> GetNCAs() const;
|
||||
std::shared_ptr<NCA> GetNCA(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetNCAFile(u64 title_id, ContentRecordType type) const;
|
||||
std::vector<Core::Crypto::Key128> GetTitlekey() const;
|
||||
|
||||
std::vector<VirtualFile> GetFiles() const override;
|
||||
@@ -70,7 +67,7 @@ private:
|
||||
|
||||
std::shared_ptr<PartitionFilesystem> pfs;
|
||||
// Map title id -> {map type -> NCA}
|
||||
std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> ncas;
|
||||
std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> ncas;
|
||||
std::vector<VirtualFile> ticket_files;
|
||||
|
||||
Core::Crypto::KeyManager keys;
|
||||
|
||||
@@ -12,23 +12,6 @@
|
||||
|
||||
namespace Core::Frontend {
|
||||
|
||||
/**
|
||||
* Represents a graphics context that can be used for background computation or drawing. If the
|
||||
* graphics backend doesn't require the context, then the implementation of these methods can be
|
||||
* stubs
|
||||
*/
|
||||
class GraphicsContext {
|
||||
public:
|
||||
/// Makes the graphics context current for the caller thread
|
||||
virtual void MakeCurrent() = 0;
|
||||
|
||||
/// Releases (dunno if this is the "right" word) the context from the caller thread
|
||||
virtual void DoneCurrent() = 0;
|
||||
|
||||
/// Swap buffers to display the next frame
|
||||
virtual void SwapBuffers() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstraction class used to provide an interface between emulation code and the frontend
|
||||
* (e.g. SDL, QGLWidget, GLFW, etc...).
|
||||
@@ -47,7 +30,7 @@ public:
|
||||
* - DO NOT TREAT THIS CLASS AS A GUI TOOLKIT ABSTRACTION LAYER. That's not what it is. Please
|
||||
* re-read the upper points again and think about it if you don't see this.
|
||||
*/
|
||||
class EmuWindow : public GraphicsContext {
|
||||
class EmuWindow {
|
||||
public:
|
||||
/// Data structure to store emuwindow configuration
|
||||
struct WindowConfig {
|
||||
@@ -57,21 +40,17 @@ public:
|
||||
std::pair<unsigned, unsigned> min_client_area_size;
|
||||
};
|
||||
|
||||
/// Swap buffers to display the next frame
|
||||
virtual void SwapBuffers() = 0;
|
||||
|
||||
/// Polls window events
|
||||
virtual void PollEvents() = 0;
|
||||
|
||||
/**
|
||||
* Returns a GraphicsContext that the frontend provides that is shared with the emu window. This
|
||||
* context can be used from other threads for background graphics computation. If the frontend
|
||||
* is using a graphics backend that doesn't need anything specific to run on a different thread,
|
||||
* then it can use a stubbed implemenation for GraphicsContext.
|
||||
*
|
||||
* If the return value is null, then the core should assume that the frontend cannot provide a
|
||||
* Shared Context
|
||||
*/
|
||||
virtual std::unique_ptr<GraphicsContext> CreateSharedContext() const {
|
||||
return nullptr;
|
||||
}
|
||||
/// Makes the graphics context current for the caller thread
|
||||
virtual void MakeCurrent() = 0;
|
||||
|
||||
/// Releases (dunno if this is the "right" word) the GLFW context from the caller thread
|
||||
virtual void DoneCurrent() = 0;
|
||||
|
||||
/**
|
||||
* Signal that a touch pressed event has occurred (e.g. mouse click pressed)
|
||||
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
return name;
|
||||
}
|
||||
|
||||
static constexpr HandleType HANDLE_TYPE = HandleType::ClientPort;
|
||||
static const HandleType HANDLE_TYPE = HandleType::ClientPort;
|
||||
HandleType GetHandleType() const override {
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public:
|
||||
return name;
|
||||
}
|
||||
|
||||
static constexpr HandleType HANDLE_TYPE = HandleType::ClientSession;
|
||||
static const HandleType HANDLE_TYPE = HandleType::ClientSession;
|
||||
HandleType GetHandleType() const override {
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
|
||||
@@ -106,8 +106,6 @@ ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) {
|
||||
is_64bit_process = metadata.Is64BitProgram();
|
||||
|
||||
vm_manager.Reset(metadata.GetAddressSpaceType());
|
||||
// Ensure that the potentially resized page table is seen by CPU backends.
|
||||
Memory::SetCurrentPageTable(&vm_manager.page_table);
|
||||
|
||||
const auto& caps = metadata.GetKernelCapabilities();
|
||||
const auto capability_init_result =
|
||||
|
||||
@@ -85,7 +85,7 @@ public:
|
||||
return name;
|
||||
}
|
||||
|
||||
static constexpr HandleType HANDLE_TYPE = HandleType::Process;
|
||||
static const HandleType HANDLE_TYPE = HandleType::Process;
|
||||
HandleType GetHandleType() const override {
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
return reset_type;
|
||||
}
|
||||
|
||||
static constexpr HandleType HANDLE_TYPE = HandleType::ReadableEvent;
|
||||
static const HandleType HANDLE_TYPE = HandleType::ReadableEvent;
|
||||
HandleType GetHandleType() const override {
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
return GetTypeName();
|
||||
}
|
||||
|
||||
static constexpr HandleType HANDLE_TYPE = HandleType::ResourceLimit;
|
||||
static const HandleType HANDLE_TYPE = HandleType::ResourceLimit;
|
||||
HandleType GetHandleType() const override {
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public:
|
||||
return name;
|
||||
}
|
||||
|
||||
static constexpr HandleType HANDLE_TYPE = HandleType::ServerPort;
|
||||
static const HandleType HANDLE_TYPE = HandleType::ServerPort;
|
||||
HandleType GetHandleType() const override {
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
|
||||
@@ -28,9 +28,11 @@ ServerSession::~ServerSession() {
|
||||
// the emulated application.
|
||||
|
||||
// Decrease the port's connection count.
|
||||
if (parent->port) {
|
||||
if (parent->port)
|
||||
parent->port->ConnectionClosed();
|
||||
}
|
||||
|
||||
// TODO(Subv): Wake up all the ClientSession's waiting threads and set
|
||||
// the SendSyncRequest result to 0xC920181A.
|
||||
|
||||
parent->server = nullptr;
|
||||
}
|
||||
@@ -72,6 +74,9 @@ void ServerSession::ClientDisconnected() {
|
||||
handler->ClientDisconnected(this);
|
||||
}
|
||||
|
||||
// TODO(Subv): Force a wake up of all the ServerSession's waiting threads and set
|
||||
// their WaitSynchronization result to 0xC920181A.
|
||||
|
||||
// Clean up the list of client threads with pending requests, they are unneeded now that the
|
||||
// client endpoint is closed.
|
||||
pending_requesting_threads.clear();
|
||||
|
||||
@@ -46,7 +46,7 @@ public:
|
||||
return name;
|
||||
}
|
||||
|
||||
static constexpr HandleType HANDLE_TYPE = HandleType::ServerSession;
|
||||
static const HandleType HANDLE_TYPE = HandleType::ServerSession;
|
||||
HandleType GetHandleType() const override {
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ public:
|
||||
return name;
|
||||
}
|
||||
|
||||
static constexpr HandleType HANDLE_TYPE = HandleType::SharedMemory;
|
||||
static const HandleType HANDLE_TYPE = HandleType::SharedMemory;
|
||||
HandleType GetHandleType() const override {
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,12 +6,8 @@
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
void CallSVC(Core::System& system, u32 immediate);
|
||||
void CallSVC(u32 immediate);
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -11,312 +11,278 @@
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
static inline u64 Param(const Core::System& system, int n) {
|
||||
return system.CurrentArmInterface().GetReg(n);
|
||||
static inline u64 Param(int n) {
|
||||
return Core::CurrentArmInterface().GetReg(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* HLE a function return from the current ARM userland process
|
||||
* @param system System context
|
||||
* @param result Result to return
|
||||
* @param res Result to return
|
||||
*/
|
||||
static inline void FuncReturn(Core::System& system, u64 result) {
|
||||
system.CurrentArmInterface().SetReg(0, result);
|
||||
static inline void FuncReturn(u64 res) {
|
||||
Core::CurrentArmInterface().SetReg(0, res);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Function wrappers that return type ResultCode
|
||||
|
||||
template <ResultCode func(Core::System&, u64)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
FuncReturn(system, func(system, Param(system, 0)).raw);
|
||||
template <ResultCode func(u64)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func(Param(0)).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u32)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
FuncReturn(system, func(system, static_cast<u32>(Param(system, 0))).raw);
|
||||
template <ResultCode func(u32)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func(static_cast<u32>(Param(0))).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u32, u32)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
FuncReturn(
|
||||
system,
|
||||
func(system, static_cast<u32>(Param(system, 0)), static_cast<u32>(Param(system, 1))).raw);
|
||||
template <ResultCode func(u32, u32)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func(static_cast<u32>(Param(0)), static_cast<u32>(Param(1))).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u32*)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
template <ResultCode func(u32*)>
|
||||
void SvcWrap() {
|
||||
u32 param = 0;
|
||||
const u32 retval = func(system, ¶m).raw;
|
||||
system.CurrentArmInterface().SetReg(1, param);
|
||||
FuncReturn(system, retval);
|
||||
const u32 retval = func(¶m).raw;
|
||||
Core::CurrentArmInterface().SetReg(1, param);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u32*, u32)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
template <ResultCode func(u32*, u32)>
|
||||
void SvcWrap() {
|
||||
u32 param_1 = 0;
|
||||
const u32 retval = func(system, ¶m_1, static_cast<u32>(Param(system, 1))).raw;
|
||||
system.CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(system, retval);
|
||||
u32 retval = func(¶m_1, static_cast<u32>(Param(1))).raw;
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u32*, u32*)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
template <ResultCode func(u32*, u32*)>
|
||||
void SvcWrap() {
|
||||
u32 param_1 = 0;
|
||||
u32 param_2 = 0;
|
||||
const u32 retval = func(system, ¶m_1, ¶m_2).raw;
|
||||
const u32 retval = func(¶m_1, ¶m_2).raw;
|
||||
|
||||
auto& arm_interface = system.CurrentArmInterface();
|
||||
auto& arm_interface = Core::CurrentArmInterface();
|
||||
arm_interface.SetReg(1, param_1);
|
||||
arm_interface.SetReg(2, param_2);
|
||||
|
||||
FuncReturn(system, retval);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u32*, u64)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
template <ResultCode func(u32*, u64)>
|
||||
void SvcWrap() {
|
||||
u32 param_1 = 0;
|
||||
const u32 retval = func(system, ¶m_1, Param(system, 1)).raw;
|
||||
system.CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(system, retval);
|
||||
const u32 retval = func(¶m_1, Param(1)).raw;
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u32*, u64, u32)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
template <ResultCode func(u32*, u64, u32)>
|
||||
void SvcWrap() {
|
||||
u32 param_1 = 0;
|
||||
const u32 retval =
|
||||
func(system, ¶m_1, Param(system, 1), static_cast<u32>(Param(system, 2))).raw;
|
||||
|
||||
system.CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(system, retval);
|
||||
const u32 retval = func(¶m_1, Param(1), static_cast<u32>(Param(2))).raw;
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u64*, u32)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
template <ResultCode func(u64*, u32)>
|
||||
void SvcWrap() {
|
||||
u64 param_1 = 0;
|
||||
const u32 retval = func(system, ¶m_1, static_cast<u32>(Param(system, 1))).raw;
|
||||
|
||||
system.CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(system, retval);
|
||||
const u32 retval = func(¶m_1, static_cast<u32>(Param(1))).raw;
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u64, s32)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
FuncReturn(system, func(system, Param(system, 0), static_cast<s32>(Param(system, 1))).raw);
|
||||
template <ResultCode func(u64, s32)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func(Param(0), static_cast<s32>(Param(1))).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u64, u32)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
FuncReturn(system, func(system, Param(system, 0), static_cast<u32>(Param(system, 1))).raw);
|
||||
template <ResultCode func(u64, u32)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func(Param(0), static_cast<u32>(Param(1))).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u64*, u64)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
template <ResultCode func(u64*, u64)>
|
||||
void SvcWrap() {
|
||||
u64 param_1 = 0;
|
||||
const u32 retval = func(system, ¶m_1, Param(system, 1)).raw;
|
||||
|
||||
system.CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(system, retval);
|
||||
u32 retval = func(¶m_1, Param(1)).raw;
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u64*, u32, u32)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
template <ResultCode func(u64*, u32, u32)>
|
||||
void SvcWrap() {
|
||||
u64 param_1 = 0;
|
||||
const u32 retval = func(system, ¶m_1, static_cast<u32>(Param(system, 1)),
|
||||
static_cast<u32>(Param(system, 2)))
|
||||
.raw;
|
||||
|
||||
system.CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(system, retval);
|
||||
u32 retval = func(¶m_1, static_cast<u32>(Param(1)), static_cast<u32>(Param(2))).raw;
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u32, u64)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)), Param(system, 1)).raw);
|
||||
template <ResultCode func(u32, u64)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func(static_cast<u32>(Param(0)), Param(1)).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u32, u32, u64)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)),
|
||||
static_cast<u32>(Param(system, 1)), Param(system, 2))
|
||||
.raw);
|
||||
template <ResultCode func(u32, u32, u64)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func(static_cast<u32>(Param(0)), static_cast<u32>(Param(1)), Param(2)).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u32, u32*, u64*)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
template <ResultCode func(u32, u32*, u64*)>
|
||||
void SvcWrap() {
|
||||
u32 param_1 = 0;
|
||||
u64 param_2 = 0;
|
||||
const ResultCode retval = func(system, static_cast<u32>(Param(system, 2)), ¶m_1, ¶m_2);
|
||||
|
||||
system.CurrentArmInterface().SetReg(1, param_1);
|
||||
system.CurrentArmInterface().SetReg(2, param_2);
|
||||
FuncReturn(system, retval.raw);
|
||||
ResultCode retval = func(static_cast<u32>(Param(2)), ¶m_1, ¶m_2);
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
Core::CurrentArmInterface().SetReg(2, param_2);
|
||||
FuncReturn(retval.raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u64, u64, u32, u32)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
FuncReturn(system, func(system, Param(system, 0), Param(system, 1),
|
||||
static_cast<u32>(Param(system, 2)), static_cast<u32>(Param(system, 3)))
|
||||
.raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u64, u64, u32, u64)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
FuncReturn(system, func(system, Param(system, 0), Param(system, 1),
|
||||
static_cast<u32>(Param(system, 2)), Param(system, 3))
|
||||
.raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u32, u64, u32)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)), Param(system, 1),
|
||||
static_cast<u32>(Param(system, 2)))
|
||||
.raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u64, u64, u64)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
FuncReturn(system, func(system, Param(system, 0), Param(system, 1), Param(system, 2)).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u64, u64, u32)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
template <ResultCode func(u64, u64, u32, u32)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(
|
||||
system,
|
||||
func(system, Param(system, 0), Param(system, 1), static_cast<u32>(Param(system, 2))).raw);
|
||||
func(Param(0), Param(1), static_cast<u32>(Param(2)), static_cast<u32>(Param(3))).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u32, u64, u64, u32)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)), Param(system, 1),
|
||||
Param(system, 2), static_cast<u32>(Param(system, 3)))
|
||||
.raw);
|
||||
template <ResultCode func(u64, u64, u32, u64)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func(Param(0), Param(1), static_cast<u32>(Param(2)), Param(3)).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u32, u64, u64)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
template <ResultCode func(u32, u64, u32)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func(static_cast<u32>(Param(0)), Param(1), static_cast<u32>(Param(2))).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u64, u64, u64)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func(Param(0), Param(1), Param(2)).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u64, u64, u32)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func(Param(0), Param(1), static_cast<u32>(Param(2))).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u32, u64, u64, u32)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(
|
||||
system,
|
||||
func(system, static_cast<u32>(Param(system, 0)), Param(system, 1), Param(system, 2)).raw);
|
||||
func(static_cast<u32>(Param(0)), Param(1), Param(2), static_cast<u32>(Param(3))).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u32*, u64, u64, s64)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
template <ResultCode func(u32, u64, u64)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func(static_cast<u32>(Param(0)), Param(1), Param(2)).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u32*, u64, u64, s64)>
|
||||
void SvcWrap() {
|
||||
u32 param_1 = 0;
|
||||
const u32 retval = func(system, ¶m_1, Param(system, 1), static_cast<u32>(Param(system, 2)),
|
||||
static_cast<s64>(Param(system, 3)))
|
||||
.raw;
|
||||
|
||||
system.CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(system, retval);
|
||||
ResultCode retval =
|
||||
func(¶m_1, Param(1), static_cast<u32>(Param(2)), static_cast<s64>(Param(3)));
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval.raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u64, u64, u32, s64)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
FuncReturn(system, func(system, Param(system, 0), Param(system, 1),
|
||||
static_cast<u32>(Param(system, 2)), static_cast<s64>(Param(system, 3)))
|
||||
.raw);
|
||||
template <ResultCode func(u64, u64, u32, s64)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(
|
||||
func(Param(0), Param(1), static_cast<u32>(Param(2)), static_cast<s64>(Param(3))).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u64*, u64, u64, u64)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
template <ResultCode func(u64*, u64, u64, u64)>
|
||||
void SvcWrap() {
|
||||
u64 param_1 = 0;
|
||||
const u32 retval =
|
||||
func(system, ¶m_1, Param(system, 1), Param(system, 2), Param(system, 3)).raw;
|
||||
|
||||
system.CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(system, retval);
|
||||
u32 retval = func(¶m_1, Param(1), Param(2), Param(3)).raw;
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u32*, u64, u64, u64, u32, s32)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
template <ResultCode func(u32*, u64, u64, u64, u32, s32)>
|
||||
void SvcWrap() {
|
||||
u32 param_1 = 0;
|
||||
const u32 retval = func(system, ¶m_1, Param(system, 1), Param(system, 2), Param(system, 3),
|
||||
static_cast<u32>(Param(system, 4)), static_cast<s32>(Param(system, 5)))
|
||||
.raw;
|
||||
|
||||
system.CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(system, retval);
|
||||
u32 retval = func(¶m_1, Param(1), Param(2), Param(3), static_cast<u32>(Param(4)),
|
||||
static_cast<s32>(Param(5)))
|
||||
.raw;
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u32*, u64, u64, u32)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
template <ResultCode func(u32*, u64, u64, u32)>
|
||||
void SvcWrap() {
|
||||
u32 param_1 = 0;
|
||||
const u32 retval = func(system, ¶m_1, Param(system, 1), Param(system, 2),
|
||||
static_cast<u32>(Param(system, 3)))
|
||||
.raw;
|
||||
|
||||
system.CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(system, retval);
|
||||
u32 retval = func(¶m_1, Param(1), Param(2), static_cast<u32>(Param(3))).raw;
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, Handle*, u64, u32, u32)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
template <ResultCode func(Handle*, u64, u32, u32)>
|
||||
void SvcWrap() {
|
||||
u32 param_1 = 0;
|
||||
const u32 retval = func(system, ¶m_1, Param(system, 1), static_cast<u32>(Param(system, 2)),
|
||||
static_cast<u32>(Param(system, 3)))
|
||||
.raw;
|
||||
|
||||
system.CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(system, retval);
|
||||
u32 retval =
|
||||
func(¶m_1, Param(1), static_cast<u32>(Param(2)), static_cast<u32>(Param(3))).raw;
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u64, u32, s32, s64)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
FuncReturn(system, func(system, Param(system, 0), static_cast<u32>(Param(system, 1)),
|
||||
static_cast<s32>(Param(system, 2)), static_cast<s64>(Param(system, 3)))
|
||||
.raw);
|
||||
template <ResultCode func(u64, u32, s32, s64)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func(Param(0), static_cast<u32>(Param(1)), static_cast<s32>(Param(2)),
|
||||
static_cast<s64>(Param(3)))
|
||||
.raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u64, u32, s32, s32)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
FuncReturn(system, func(system, Param(system, 0), static_cast<u32>(Param(system, 1)),
|
||||
static_cast<s32>(Param(system, 2)), static_cast<s32>(Param(system, 3)))
|
||||
.raw);
|
||||
template <ResultCode func(u64, u32, s32, s32)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func(Param(0), static_cast<u32>(Param(1)), static_cast<s32>(Param(2)),
|
||||
static_cast<s32>(Param(3)))
|
||||
.raw);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Function wrappers that return type u32
|
||||
|
||||
template <u32 func(Core::System&)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
FuncReturn(system, func(system));
|
||||
template <u32 func()>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Function wrappers that return type u64
|
||||
|
||||
template <u64 func(Core::System&)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
FuncReturn(system, func(system));
|
||||
template <u64 func()>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// Function wrappers that return type void
|
||||
|
||||
template <void func(Core::System&)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
func(system);
|
||||
template <void func()>
|
||||
void SvcWrap() {
|
||||
func();
|
||||
}
|
||||
|
||||
template <void func(Core::System&, s64)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
func(system, static_cast<s64>(Param(system, 0)));
|
||||
template <void func(s64)>
|
||||
void SvcWrap() {
|
||||
func(static_cast<s64>(Param(0)));
|
||||
}
|
||||
|
||||
template <void func(Core::System&, u64, u64)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
func(system, Param(system, 0), Param(system, 1));
|
||||
template <void func(u64, u64 len)>
|
||||
void SvcWrap() {
|
||||
func(Param(0), Param(1));
|
||||
}
|
||||
|
||||
template <void func(Core::System&, u64, u64, u64)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
func(system, Param(system, 0), Param(system, 1), Param(system, 2));
|
||||
template <void func(u64, u64, u64)>
|
||||
void SvcWrap() {
|
||||
func(Param(0), Param(1), Param(2));
|
||||
}
|
||||
|
||||
template <void func(Core::System&, u32, u64, u64)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
func(system, static_cast<u32>(Param(system, 0)), Param(system, 1), Param(system, 2));
|
||||
template <void func(u32, u64, u64)>
|
||||
void SvcWrap() {
|
||||
func(static_cast<u32>(Param(0)), Param(1), Param(2));
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -106,7 +106,7 @@ public:
|
||||
return "Thread";
|
||||
}
|
||||
|
||||
static constexpr HandleType HANDLE_TYPE = HandleType::Thread;
|
||||
static const HandleType HANDLE_TYPE = HandleType::Thread;
|
||||
HandleType GetHandleType() const override {
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public:
|
||||
return name;
|
||||
}
|
||||
|
||||
static constexpr HandleType HANDLE_TYPE = HandleType::WritableEvent;
|
||||
static const HandleType HANDLE_TYPE = HandleType::WritableEvent;
|
||||
HandleType GetHandleType() const override {
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ static FileSys::VirtualFile GetManualRomFS() {
|
||||
if (loader.ReadManualRomFS(out) == Loader::ResultStatus::Success)
|
||||
return out;
|
||||
|
||||
const auto& installed{Core::System::GetInstance().GetContentProvider()};
|
||||
const auto& installed{FileSystem::GetUnionContents()};
|
||||
const auto res = installed.GetEntry(Core::System::GetInstance().CurrentProcess()->GetTitleID(),
|
||||
FileSys::ContentRecordType::Manual);
|
||||
|
||||
|
||||
@@ -33,11 +33,11 @@ static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) {
|
||||
|
||||
static std::vector<u64> AccumulateAOCTitleIDs() {
|
||||
std::vector<u64> add_on_content;
|
||||
const auto& rcu = Core::System::GetInstance().GetContentProvider();
|
||||
const auto rcu = FileSystem::GetUnionContents();
|
||||
const auto list =
|
||||
rcu.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
|
||||
std::transform(list.begin(), list.end(), std::back_inserter(add_on_content),
|
||||
[](const FileSys::ContentProviderEntry& rce) { return rce.title_id; });
|
||||
[](const FileSys::RegisteredCacheEntry& rce) { return rce.title_id; });
|
||||
add_on_content.erase(
|
||||
std::remove_if(
|
||||
add_on_content.begin(), add_on_content.end(),
|
||||
|
||||
@@ -391,6 +391,11 @@ void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
|
||||
save_data_factory->WriteSaveDataSize(type, title_id, user_id, new_value);
|
||||
}
|
||||
|
||||
FileSys::RegisteredCacheUnion GetUnionContents() {
|
||||
return FileSys::RegisteredCacheUnion{
|
||||
{GetSystemNANDContents(), GetUserNANDContents(), GetSDMCContents()}};
|
||||
}
|
||||
|
||||
FileSys::RegisteredCache* GetSystemNANDContents() {
|
||||
LOG_TRACE(Service_FS, "Opening System NAND Contents");
|
||||
|
||||
@@ -455,10 +460,6 @@ void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
|
||||
if (bis_factory == nullptr) {
|
||||
bis_factory =
|
||||
std::make_unique<FileSys::BISFactory>(nand_directory, load_directory, dump_directory);
|
||||
Core::System::GetInstance().RegisterContentProvider(
|
||||
FileSys::ContentProviderUnionSlot::SysNAND, bis_factory->GetSystemNANDContents());
|
||||
Core::System::GetInstance().RegisterContentProvider(
|
||||
FileSys::ContentProviderUnionSlot::UserNAND, bis_factory->GetUserNANDContents());
|
||||
}
|
||||
|
||||
if (save_data_factory == nullptr) {
|
||||
@@ -467,8 +468,6 @@ void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
|
||||
|
||||
if (sdmc_factory == nullptr) {
|
||||
sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory));
|
||||
Core::System::GetInstance().RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC,
|
||||
sdmc_factory->GetSDMCContents());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,8 @@ FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id,
|
||||
void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
|
||||
FileSys::SaveDataSize new_value);
|
||||
|
||||
FileSys::RegisteredCacheUnion GetUnionContents();
|
||||
|
||||
FileSys::RegisteredCache* GetSystemNANDContents();
|
||||
FileSys::RegisteredCache* GetUserNANDContents();
|
||||
FileSys::RegisteredCache* GetSDMCContents();
|
||||
|
||||
@@ -115,12 +115,11 @@ private:
|
||||
|
||||
void Read(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u64 option = rp.Pop<u64>();
|
||||
const u64 unk = rp.Pop<u64>();
|
||||
const s64 offset = rp.Pop<s64>();
|
||||
const s64 length = rp.Pop<s64>();
|
||||
|
||||
LOG_DEBUG(Service_FS, "called, option={}, offset=0x{:X}, length={}", option, offset,
|
||||
length);
|
||||
LOG_DEBUG(Service_FS, "called, offset=0x{:X}, length={}", offset, length);
|
||||
|
||||
// Error checking
|
||||
if (length < 0) {
|
||||
@@ -149,12 +148,11 @@ private:
|
||||
|
||||
void Write(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u64 option = rp.Pop<u64>();
|
||||
const u64 unk = rp.Pop<u64>();
|
||||
const s64 offset = rp.Pop<s64>();
|
||||
const s64 length = rp.Pop<s64>();
|
||||
|
||||
LOG_DEBUG(Service_FS, "called, option={}, offset=0x{:X}, length={}", option, offset,
|
||||
length);
|
||||
LOG_DEBUG(Service_FS, "called, offset=0x{:X}, length={}", offset, length);
|
||||
|
||||
// Error checking
|
||||
if (length < 0) {
|
||||
@@ -252,7 +250,10 @@ private:
|
||||
u64 next_entry_index = 0;
|
||||
|
||||
void Read(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_FS, "called.");
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u64 unk = rp.Pop<u64>();
|
||||
|
||||
LOG_DEBUG(Service_FS, "called, unk=0x{:X}", unk);
|
||||
|
||||
// Calculate how many entries we can fit in the output buffer
|
||||
const u64 count_entries = ctx.GetWriteBufferSize() / sizeof(FileSys::Entry);
|
||||
|
||||
@@ -93,18 +93,12 @@ public:
|
||||
}
|
||||
|
||||
void LoadNrr(Kernel::HLERequestContext& ctx) {
|
||||
struct Parameters {
|
||||
u64_le process_id;
|
||||
u64_le nrr_address;
|
||||
u64_le nrr_size;
|
||||
};
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto [process_id, nrr_address, nrr_size] = rp.PopRaw<Parameters>();
|
||||
|
||||
LOG_DEBUG(Service_LDR,
|
||||
"called with process_id={:016X}, nrr_address={:016X}, nrr_size={:016X}",
|
||||
process_id, nrr_address, nrr_size);
|
||||
rp.Skip(2, false);
|
||||
const VAddr nrr_addr{rp.Pop<VAddr>()};
|
||||
const u64 nrr_size{rp.Pop<u64>()};
|
||||
LOG_DEBUG(Service_LDR, "called with nrr_addr={:016X}, nrr_size={:016X}", nrr_addr,
|
||||
nrr_size);
|
||||
|
||||
if (!initialized) {
|
||||
LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!");
|
||||
@@ -122,26 +116,24 @@ public:
|
||||
}
|
||||
|
||||
// NRR Address does not fall on 0x1000 byte boundary
|
||||
if (!Common::Is4KBAligned(nrr_address)) {
|
||||
LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!",
|
||||
nrr_address);
|
||||
if (!Common::Is4KBAligned(nrr_addr)) {
|
||||
LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!", nrr_addr);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_ALIGNMENT);
|
||||
return;
|
||||
}
|
||||
|
||||
// NRR Size is zero or causes overflow
|
||||
if (nrr_address + nrr_size <= nrr_address || nrr_size == 0 ||
|
||||
!Common::Is4KBAligned(nrr_size)) {
|
||||
if (nrr_addr + nrr_size <= nrr_addr || nrr_size == 0 || !Common::Is4KBAligned(nrr_size)) {
|
||||
LOG_ERROR(Service_LDR, "NRR Size is invalid! (nrr_address={:016X}, nrr_size={:016X})",
|
||||
nrr_address, nrr_size);
|
||||
nrr_addr, nrr_size);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_SIZE);
|
||||
return;
|
||||
}
|
||||
// Read NRR data from memory
|
||||
std::vector<u8> nrr_data(nrr_size);
|
||||
Memory::ReadBlock(nrr_address, nrr_data.data(), nrr_size);
|
||||
Memory::ReadBlock(nrr_addr, nrr_data.data(), nrr_size);
|
||||
NRRHeader header;
|
||||
std::memcpy(&header, nrr_data.data(), sizeof(NRRHeader));
|
||||
|
||||
@@ -182,7 +174,7 @@ public:
|
||||
hashes.emplace_back(hash);
|
||||
}
|
||||
|
||||
nrr.insert_or_assign(nrr_address, std::move(hashes));
|
||||
nrr.insert_or_assign(nrr_addr, std::move(hashes));
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -196,30 +188,23 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
struct Parameters {
|
||||
u64_le process_id;
|
||||
u64_le nrr_address;
|
||||
};
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto [process_id, nrr_address] = rp.PopRaw<Parameters>();
|
||||
rp.Skip(2, false);
|
||||
const auto nrr_addr{rp.Pop<VAddr>()};
|
||||
LOG_DEBUG(Service_LDR, "called with nrr_addr={:016X}", nrr_addr);
|
||||
|
||||
LOG_DEBUG(Service_LDR, "called with process_id={:016X}, nrr_addr={:016X}", process_id,
|
||||
nrr_address);
|
||||
|
||||
if (!Common::Is4KBAligned(nrr_address)) {
|
||||
LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!",
|
||||
nrr_address);
|
||||
if (!Common::Is4KBAligned(nrr_addr)) {
|
||||
LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!", nrr_addr);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_ALIGNMENT);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto iter = nrr.find(nrr_address);
|
||||
const auto iter = nrr.find(nrr_addr);
|
||||
if (iter == nrr.end()) {
|
||||
LOG_ERROR(Service_LDR,
|
||||
"Attempting to unload NRR which has not been loaded! (addr={:016X})",
|
||||
nrr_address);
|
||||
nrr_addr);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_NRR_ADDRESS);
|
||||
return;
|
||||
@@ -231,22 +216,16 @@ public:
|
||||
}
|
||||
|
||||
void LoadNro(Kernel::HLERequestContext& ctx) {
|
||||
struct Parameters {
|
||||
u64_le process_id;
|
||||
u64_le image_address;
|
||||
u64_le image_size;
|
||||
u64_le bss_address;
|
||||
u64_le bss_size;
|
||||
};
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto [process_id, nro_address, nro_size, bss_address, bss_size] =
|
||||
rp.PopRaw<Parameters>();
|
||||
|
||||
LOG_DEBUG(Service_LDR,
|
||||
"called with pid={:016X}, nro_addr={:016X}, nro_size={:016X}, bss_addr={:016X}, "
|
||||
"bss_size={:016X}",
|
||||
process_id, nro_address, nro_size, bss_address, bss_size);
|
||||
rp.Skip(2, false);
|
||||
const VAddr nro_addr{rp.Pop<VAddr>()};
|
||||
const u64 nro_size{rp.Pop<u64>()};
|
||||
const VAddr bss_addr{rp.Pop<VAddr>()};
|
||||
const u64 bss_size{rp.Pop<u64>()};
|
||||
LOG_DEBUG(
|
||||
Service_LDR,
|
||||
"called with nro_addr={:016X}, nro_size={:016X}, bss_addr={:016X}, bss_size={:016X}",
|
||||
nro_addr, nro_size, bss_addr, bss_size);
|
||||
|
||||
if (!initialized) {
|
||||
LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!");
|
||||
@@ -264,9 +243,8 @@ public:
|
||||
}
|
||||
|
||||
// NRO Address does not fall on 0x1000 byte boundary
|
||||
if (!Common::Is4KBAligned(nro_address)) {
|
||||
LOG_ERROR(Service_LDR, "NRO Address has invalid alignment (actual {:016X})!",
|
||||
nro_address);
|
||||
if (!Common::Is4KBAligned(nro_addr)) {
|
||||
LOG_ERROR(Service_LDR, "NRO Address has invalid alignment (actual {:016X})!", nro_addr);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_ALIGNMENT);
|
||||
return;
|
||||
@@ -274,15 +252,15 @@ public:
|
||||
|
||||
// NRO Size or BSS Size is zero or causes overflow
|
||||
const auto nro_size_valid =
|
||||
nro_size != 0 && nro_address + nro_size > nro_address && Common::Is4KBAligned(nro_size);
|
||||
const auto bss_size_valid = nro_size + bss_size >= nro_size &&
|
||||
(bss_size == 0 || bss_address + bss_size > bss_address);
|
||||
nro_size != 0 && nro_addr + nro_size > nro_addr && Common::Is4KBAligned(nro_size);
|
||||
const auto bss_size_valid =
|
||||
nro_size + bss_size >= nro_size && (bss_size == 0 || bss_addr + bss_size > bss_addr);
|
||||
|
||||
if (!nro_size_valid || !bss_size_valid) {
|
||||
LOG_ERROR(Service_LDR,
|
||||
"NRO Size or BSS Size is invalid! (nro_address={:016X}, nro_size={:016X}, "
|
||||
"bss_address={:016X}, bss_size={:016X})",
|
||||
nro_address, nro_size, bss_address, bss_size);
|
||||
nro_addr, nro_size, bss_addr, bss_size);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_SIZE);
|
||||
return;
|
||||
@@ -290,7 +268,7 @@ public:
|
||||
|
||||
// Read NRO data from memory
|
||||
std::vector<u8> nro_data(nro_size);
|
||||
Memory::ReadBlock(nro_address, nro_data.data(), nro_size);
|
||||
Memory::ReadBlock(nro_addr, nro_data.data(), nro_size);
|
||||
|
||||
SHA256Hash hash{};
|
||||
mbedtls_sha256(nro_data.data(), nro_data.size(), hash.data(), 0);
|
||||
@@ -340,18 +318,17 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT(
|
||||
vm_manager
|
||||
.MirrorMemory(*map_address, nro_address, nro_size, Kernel::MemoryState::ModuleCode)
|
||||
.IsSuccess());
|
||||
ASSERT(vm_manager.UnmapRange(nro_address, nro_size).IsSuccess());
|
||||
ASSERT(vm_manager
|
||||
.MirrorMemory(*map_address, nro_addr, nro_size, Kernel::MemoryState::ModuleCode)
|
||||
.IsSuccess());
|
||||
ASSERT(vm_manager.UnmapRange(nro_addr, nro_size).IsSuccess());
|
||||
|
||||
if (bss_size > 0) {
|
||||
ASSERT(vm_manager
|
||||
.MirrorMemory(*map_address + nro_size, bss_address, bss_size,
|
||||
.MirrorMemory(*map_address + nro_size, bss_addr, bss_size,
|
||||
Kernel::MemoryState::ModuleCode)
|
||||
.IsSuccess());
|
||||
ASSERT(vm_manager.UnmapRange(bss_address, bss_size).IsSuccess());
|
||||
ASSERT(vm_manager.UnmapRange(bss_addr, bss_size).IsSuccess());
|
||||
}
|
||||
|
||||
vm_manager.ReprotectRange(*map_address, header.text_size,
|
||||
@@ -371,6 +348,13 @@ public:
|
||||
}
|
||||
|
||||
void UnloadNro(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
rp.Skip(2, false);
|
||||
const VAddr mapped_addr{rp.PopRaw<VAddr>()};
|
||||
const VAddr heap_addr{rp.PopRaw<VAddr>()};
|
||||
LOG_DEBUG(Service_LDR, "called with mapped_addr={:016X}, heap_addr={:016X}", mapped_addr,
|
||||
heap_addr);
|
||||
|
||||
if (!initialized) {
|
||||
LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
@@ -378,30 +362,22 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
struct Parameters {
|
||||
u64_le process_id;
|
||||
u64_le nro_address;
|
||||
};
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto [process_id, nro_address] = rp.PopRaw<Parameters>();
|
||||
LOG_DEBUG(Service_LDR, "called with process_id={:016X}, nro_address=0x{:016X}", process_id,
|
||||
nro_address);
|
||||
|
||||
if (!Common::Is4KBAligned(nro_address)) {
|
||||
LOG_ERROR(Service_LDR, "NRO address has invalid alignment (nro_address=0x{:016X})",
|
||||
nro_address);
|
||||
if (!Common::Is4KBAligned(mapped_addr) || !Common::Is4KBAligned(heap_addr)) {
|
||||
LOG_ERROR(Service_LDR,
|
||||
"NRO/BSS Address has invalid alignment (actual nro_addr={:016X}, "
|
||||
"bss_addr={:016X})!",
|
||||
mapped_addr, heap_addr);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_ALIGNMENT);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto iter = nro.find(nro_address);
|
||||
const auto iter = nro.find(mapped_addr);
|
||||
if (iter == nro.end()) {
|
||||
LOG_ERROR(Service_LDR,
|
||||
"The NRO attempting to be unmapped was not mapped or has an invalid address "
|
||||
"(nro_address=0x{:016X})!",
|
||||
nro_address);
|
||||
"The NRO attempting to unmap was not mapped or has an invalid address "
|
||||
"(actual {:016X})!",
|
||||
mapped_addr);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_NRO_ADDRESS);
|
||||
return;
|
||||
@@ -410,7 +386,10 @@ public:
|
||||
auto& vm_manager = Core::CurrentProcess()->VMManager();
|
||||
const auto& nro_size = iter->second.size;
|
||||
|
||||
ASSERT(vm_manager.UnmapRange(nro_address, nro_size).IsSuccess());
|
||||
ASSERT(vm_manager
|
||||
.MirrorMemory(heap_addr, mapped_addr, nro_size, Kernel::MemoryState::ModuleCode)
|
||||
.IsSuccess());
|
||||
ASSERT(vm_manager.UnmapRange(mapped_addr, nro_size).IsSuccess());
|
||||
|
||||
Core::System::GetInstance().InvalidateCpuInstructionCaches();
|
||||
|
||||
@@ -480,10 +459,11 @@ private:
|
||||
std::map<VAddr, NROInfo> nro;
|
||||
std::map<VAddr, std::vector<SHA256Hash>> nrr;
|
||||
|
||||
bool IsValidNROHash(const SHA256Hash& hash) const {
|
||||
return std::any_of(nrr.begin(), nrr.end(), [&hash](const auto& p) {
|
||||
return std::find(p.second.begin(), p.second.end(), hash) != p.second.end();
|
||||
});
|
||||
bool IsValidNROHash(const SHA256Hash& hash) {
|
||||
return std::any_of(
|
||||
nrr.begin(), nrr.end(), [&hash](const std::pair<VAddr, std::vector<SHA256Hash>>& p) {
|
||||
return std::find(p.second.begin(), p.second.end(), hash) != p.second.end();
|
||||
});
|
||||
}
|
||||
|
||||
static bool IsValidNRO(const NROHeader& header, u64 nro_size, u64 bss_size) {
|
||||
|
||||
@@ -21,13 +21,12 @@
|
||||
#include "core/hle/service/vi/display/vi_display.h"
|
||||
#include "core/hle/service/vi/layer/vi_layer.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/settings.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
namespace Service::NVFlinger {
|
||||
|
||||
constexpr s64 frame_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60);
|
||||
constexpr s64 frame_ticks_30fps = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 30);
|
||||
constexpr std::size_t SCREEN_REFRESH_RATE = 60;
|
||||
constexpr s64 frame_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / SCREEN_REFRESH_RATE);
|
||||
|
||||
NVFlinger::NVFlinger(Core::Timing::CoreTiming& core_timing) : core_timing{core_timing} {
|
||||
displays.emplace_back(0, "Default");
|
||||
@@ -37,15 +36,13 @@ NVFlinger::NVFlinger(Core::Timing::CoreTiming& core_timing) : core_timing{core_t
|
||||
displays.emplace_back(4, "Null");
|
||||
|
||||
// Schedule the screen composition events
|
||||
const auto ticks = Settings::values.force_30fps_mode ? frame_ticks_30fps : frame_ticks;
|
||||
|
||||
composition_event = core_timing.RegisterEvent(
|
||||
"ScreenComposition", [this, ticks](u64 userdata, s64 cycles_late) {
|
||||
composition_event =
|
||||
core_timing.RegisterEvent("ScreenComposition", [this](u64 userdata, s64 cycles_late) {
|
||||
Compose();
|
||||
this->core_timing.ScheduleEvent(ticks - cycles_late, composition_event);
|
||||
this->core_timing.ScheduleEvent(frame_ticks - cycles_late, composition_event);
|
||||
});
|
||||
|
||||
core_timing.ScheduleEvent(ticks, composition_event);
|
||||
core_timing.ScheduleEvent(frame_ticks, composition_event);
|
||||
}
|
||||
|
||||
NVFlinger::~NVFlinger() {
|
||||
@@ -65,7 +62,6 @@ std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) {
|
||||
const auto itr =
|
||||
std::find_if(displays.begin(), displays.end(),
|
||||
[&](const VI::Display& display) { return display.GetName() == name; });
|
||||
|
||||
if (itr == displays.end()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
#include "core/memory.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
#pragma optimize("", off)
|
||||
|
||||
namespace Loader {
|
||||
namespace {
|
||||
struct MODHeader {
|
||||
@@ -138,13 +136,13 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
|
||||
|
||||
// Apply patches if necessary
|
||||
if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) {
|
||||
std::vector<u8> pi_header;
|
||||
std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size());
|
||||
pi_header.insert(pi_header.begin(), reinterpret_cast<u8*>(&nso_header),
|
||||
reinterpret_cast<u8*>(&nso_header) + sizeof(NSOHeader));
|
||||
pi_header.insert(pi_header.begin() + sizeof(NSOHeader), program_image.begin(),
|
||||
program_image.end());
|
||||
|
||||
pi_header = pm->PatchNSO(pi_header, file.GetName());
|
||||
pi_header = pm->PatchNSO(pi_header);
|
||||
|
||||
std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.begin());
|
||||
}
|
||||
|
||||
@@ -393,7 +393,6 @@ struct Values {
|
||||
bool use_disk_shader_cache;
|
||||
bool use_accurate_gpu_emulation;
|
||||
bool use_asynchronous_gpu_emulation;
|
||||
bool force_30fps_mode;
|
||||
|
||||
float bg_red;
|
||||
float bg_green;
|
||||
|
||||
@@ -106,8 +106,6 @@ add_library(video_core STATIC
|
||||
textures/decoders.cpp
|
||||
textures/decoders.h
|
||||
textures/texture.h
|
||||
texture_cache.cpp
|
||||
texture_cache.h
|
||||
video_core.cpp
|
||||
video_core.h
|
||||
)
|
||||
@@ -129,14 +127,12 @@ if (ENABLE_VULKAN)
|
||||
renderer_vulkan/vk_sampler_cache.h
|
||||
renderer_vulkan/vk_scheduler.cpp
|
||||
renderer_vulkan/vk_scheduler.h
|
||||
renderer_vulkan/vk_shader_decompiler.cpp
|
||||
renderer_vulkan/vk_shader_decompiler.h
|
||||
renderer_vulkan/vk_stream_buffer.cpp
|
||||
renderer_vulkan/vk_stream_buffer.h
|
||||
renderer_vulkan/vk_swapchain.cpp
|
||||
renderer_vulkan/vk_swapchain.h)
|
||||
|
||||
target_include_directories(video_core PRIVATE sirit ../../externals/Vulkan-Headers/include)
|
||||
target_include_directories(video_core PRIVATE ../../externals/Vulkan-Headers/include)
|
||||
target_compile_definitions(video_core PRIVATE HAS_VULKAN)
|
||||
endif()
|
||||
|
||||
@@ -144,6 +140,3 @@ create_target_directory_groups(video_core)
|
||||
|
||||
target_link_libraries(video_core PUBLIC common core)
|
||||
target_link_libraries(video_core PRIVATE glad)
|
||||
if (ENABLE_VULKAN)
|
||||
target_link_libraries(video_core PRIVATE sirit)
|
||||
endif()
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "video_core/dma_pusher.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
|
||||
@@ -482,19 +482,8 @@ std::vector<Texture::FullTextureInfo> Maxwell3D::GetStageTextures(Regs::ShaderSt
|
||||
return textures;
|
||||
}
|
||||
|
||||
Texture::FullTextureInfo Maxwell3D::GetStageTexture(Regs::ShaderStage stage,
|
||||
std::size_t offset) const {
|
||||
auto& shader = state.shader_stages[static_cast<std::size_t>(stage)];
|
||||
auto& tex_info_buffer = shader.const_buffers[regs.tex_cb_index];
|
||||
ASSERT(tex_info_buffer.enabled && tex_info_buffer.address != 0);
|
||||
|
||||
const GPUVAddr tex_info_address =
|
||||
tex_info_buffer.address + offset * sizeof(Texture::TextureHandle);
|
||||
|
||||
ASSERT(tex_info_address < tex_info_buffer.address + tex_info_buffer.size);
|
||||
|
||||
const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(tex_info_address)};
|
||||
|
||||
Texture::FullTextureInfo Maxwell3D::GetTextureInfo(const Texture::TextureHandle tex_handle,
|
||||
std::size_t offset) const {
|
||||
Texture::FullTextureInfo tex_info{};
|
||||
tex_info.index = static_cast<u32>(offset);
|
||||
|
||||
@@ -511,6 +500,22 @@ Texture::FullTextureInfo Maxwell3D::GetStageTexture(Regs::ShaderStage stage,
|
||||
return tex_info;
|
||||
}
|
||||
|
||||
Texture::FullTextureInfo Maxwell3D::GetStageTexture(Regs::ShaderStage stage,
|
||||
std::size_t offset) const {
|
||||
const auto& shader = state.shader_stages[static_cast<std::size_t>(stage)];
|
||||
const auto& tex_info_buffer = shader.const_buffers[regs.tex_cb_index];
|
||||
ASSERT(tex_info_buffer.enabled && tex_info_buffer.address != 0);
|
||||
|
||||
const GPUVAddr tex_info_address =
|
||||
tex_info_buffer.address + offset * sizeof(Texture::TextureHandle);
|
||||
|
||||
ASSERT(tex_info_address < tex_info_buffer.address + tex_info_buffer.size);
|
||||
|
||||
const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(tex_info_address)};
|
||||
|
||||
return GetTextureInfo(tex_handle, offset);
|
||||
}
|
||||
|
||||
u32 Maxwell3D::GetRegisterValue(u32 method) const {
|
||||
ASSERT_MSG(method < Regs::NUM_REGS, "Invalid Maxwell3D register");
|
||||
return regs.reg_array[method];
|
||||
@@ -524,4 +529,12 @@ void Maxwell3D::ProcessClearBuffers() {
|
||||
rasterizer.Clear();
|
||||
}
|
||||
|
||||
u32 Maxwell3D::AccessConstBuffer32(Regs::ShaderStage stage, u64 const_buffer, u64 offset) const {
|
||||
const auto& shader_stage = state.shader_stages[static_cast<std::size_t>(stage)];
|
||||
const auto& buffer = shader_stage.const_buffers[const_buffer];
|
||||
u32 result;
|
||||
std::memcpy(&result, memory_manager.GetPointer(buffer.address + offset), sizeof(u32));
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Tegra::Engines
|
||||
|
||||
@@ -1131,12 +1131,18 @@ public:
|
||||
/// Write the value to the register identified by method.
|
||||
void CallMethod(const GPU::MethodCall& method_call);
|
||||
|
||||
/// Given a Texture Handle, returns the TSC and TIC entries.
|
||||
Texture::FullTextureInfo GetTextureInfo(const Texture::TextureHandle tex_handle,
|
||||
std::size_t offset) const;
|
||||
|
||||
/// Returns a list of enabled textures for the specified shader stage.
|
||||
std::vector<Texture::FullTextureInfo> GetStageTextures(Regs::ShaderStage stage) const;
|
||||
|
||||
/// Returns the texture information for a specific texture in a specific shader stage.
|
||||
Texture::FullTextureInfo GetStageTexture(Regs::ShaderStage stage, std::size_t offset) const;
|
||||
|
||||
u32 AccessConstBuffer32(Regs::ShaderStage stage, u64 const_buffer, u64 offset) const;
|
||||
|
||||
/// Memory for macro code - it's undetermined how big this is, however 1MB is much larger than
|
||||
/// we've seen used.
|
||||
using MacroMemory = std::array<u32, 0x40000>;
|
||||
|
||||
@@ -966,6 +966,38 @@ union Instruction {
|
||||
}
|
||||
} tex;
|
||||
|
||||
union {
|
||||
BitField<28, 1, u64> array;
|
||||
BitField<29, 2, TextureType> texture_type;
|
||||
BitField<31, 4, u64> component_mask;
|
||||
BitField<49, 1, u64> nodep_flag;
|
||||
BitField<50, 1, u64> dc_flag;
|
||||
BitField<36, 1, u64> aoffi_flag;
|
||||
BitField<37, 3, TextureProcessMode> process_mode;
|
||||
|
||||
bool IsComponentEnabled(std::size_t component) const {
|
||||
return ((1ULL << component) & component_mask) != 0;
|
||||
}
|
||||
|
||||
TextureProcessMode GetTextureProcessMode() const {
|
||||
return process_mode;
|
||||
}
|
||||
|
||||
bool UsesMiscMode(TextureMiscMode mode) const {
|
||||
switch (mode) {
|
||||
case TextureMiscMode::DC:
|
||||
return dc_flag != 0;
|
||||
case TextureMiscMode::NODEP:
|
||||
return nodep_flag != 0;
|
||||
case TextureMiscMode::AOFFI:
|
||||
return aoffi_flag != 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} tex_b;
|
||||
|
||||
union {
|
||||
BitField<22, 6, TextureQueryType> query_type;
|
||||
BitField<31, 4, u64> component_mask;
|
||||
@@ -1238,16 +1270,13 @@ union Instruction {
|
||||
|
||||
union {
|
||||
BitField<20, 16, u64> imm20_16;
|
||||
BitField<35, 1, u64> high_b_rr; // used on RR
|
||||
BitField<36, 1, u64> product_shift_left;
|
||||
BitField<37, 1, u64> merge_37;
|
||||
BitField<48, 1, u64> sign_a;
|
||||
BitField<49, 1, u64> sign_b;
|
||||
BitField<50, 2, XmadMode> mode_cbf; // used by CR, RC
|
||||
BitField<50, 3, XmadMode> mode;
|
||||
BitField<52, 1, u64> high_b;
|
||||
BitField<53, 1, u64> high_a;
|
||||
BitField<55, 1, u64> product_shift_left_second; // used on CR
|
||||
BitField<56, 1, u64> merge_56;
|
||||
} xmad;
|
||||
|
||||
@@ -1312,7 +1341,9 @@ public:
|
||||
LDG, // Load from global memory
|
||||
STG, // Store in global memory
|
||||
TEX,
|
||||
TEX_B, // Texture Load Bindless
|
||||
TXQ, // Texture Query
|
||||
TXQ_B, // Texture Query Bindless
|
||||
TEXS, // Texture Fetch with scalar/non-vec4 source/destinations
|
||||
TLDS, // Texture Load with scalar/non-vec4 source/destinations
|
||||
TLD4, // Texture Load 4
|
||||
@@ -1580,7 +1611,9 @@ private:
|
||||
INST("1110111011010---", Id::LDG, Type::Memory, "LDG"),
|
||||
INST("1110111011011---", Id::STG, Type::Memory, "STG"),
|
||||
INST("110000----111---", Id::TEX, Type::Texture, "TEX"),
|
||||
INST("1101111010111---", Id::TEX_B, Type::Texture, "TEX_B"),
|
||||
INST("1101111101001---", Id::TXQ, Type::Texture, "TXQ"),
|
||||
INST("1101111101010---", Id::TXQ_B, Type::Texture, "TXQ_B"),
|
||||
INST("1101-00---------", Id::TEXS, Type::Texture, "TEXS"),
|
||||
INST("1101101---------", Id::TLDS, Type::Texture, "TLDS"),
|
||||
INST("110010----111---", Id::TLD4, Type::Texture, "TLD4"),
|
||||
@@ -1665,7 +1698,7 @@ private:
|
||||
INST("0011011-11110---", Id::BFI_IMM_R, Type::Bfi, "BFI_IMM_R"),
|
||||
INST("0100110001000---", Id::LOP_C, Type::ArithmeticInteger, "LOP_C"),
|
||||
INST("0101110001000---", Id::LOP_R, Type::ArithmeticInteger, "LOP_R"),
|
||||
INST("0011100-01000---", Id::LOP_IMM, Type::ArithmeticInteger, "LOP_IMM"),
|
||||
INST("0011100001000---", Id::LOP_IMM, Type::ArithmeticInteger, "LOP_IMM"),
|
||||
INST("000001----------", Id::LOP32I, Type::ArithmeticIntegerImmediate, "LOP32I"),
|
||||
INST("0000001---------", Id::LOP3_C, Type::ArithmeticInteger, "LOP3_C"),
|
||||
INST("0101101111100---", Id::LOP3_R, Type::ArithmeticInteger, "LOP3_R"),
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "core/core.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_buffer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_global_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
|
||||
|
||||
@@ -299,10 +299,6 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
|
||||
BaseBindings base_bindings;
|
||||
std::array<bool, Maxwell::NumClipDistances> clip_distances{};
|
||||
|
||||
// Prepare packed bindings
|
||||
bind_ubo_pushbuffer.Setup(base_bindings.cbuf);
|
||||
bind_ssbo_pushbuffer.Setup(base_bindings.gmem);
|
||||
|
||||
for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
|
||||
const auto& shader_config = gpu.regs.shader_config[index];
|
||||
const Maxwell::ShaderProgram program{static_cast<Maxwell::ShaderProgram>(index)};
|
||||
@@ -325,8 +321,8 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
|
||||
&ubo, sizeof(ubo), static_cast<std::size_t>(uniform_buffer_alignment));
|
||||
|
||||
// Bind the emulation info buffer
|
||||
bind_ubo_pushbuffer.Push(buffer_cache.GetHandle(), offset,
|
||||
static_cast<GLsizeiptr>(sizeof(ubo)));
|
||||
glBindBufferRange(GL_UNIFORM_BUFFER, base_bindings.cbuf, buffer_cache.GetHandle(), offset,
|
||||
static_cast<GLsizeiptr>(sizeof(ubo)));
|
||||
|
||||
Shader shader{shader_cache.GetStageProgram(program)};
|
||||
const auto [program_handle, next_bindings] =
|
||||
@@ -370,9 +366,6 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
|
||||
base_bindings = next_bindings;
|
||||
}
|
||||
|
||||
bind_ubo_pushbuffer.Bind();
|
||||
bind_ssbo_pushbuffer.Bind();
|
||||
|
||||
SyncClipEnabled(clip_distances);
|
||||
|
||||
gpu.dirty_flags.shaders = false;
|
||||
@@ -907,14 +900,23 @@ void RasterizerOpenGL::SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::Shader
|
||||
const auto& shader_stage = maxwell3d.state.shader_stages[static_cast<std::size_t>(stage)];
|
||||
const auto& entries = shader->GetShaderEntries().const_buffers;
|
||||
|
||||
constexpr u64 max_binds = Tegra::Engines::Maxwell3D::Regs::MaxConstBuffers;
|
||||
std::array<GLuint, max_binds> bind_buffers;
|
||||
std::array<GLintptr, max_binds> bind_offsets;
|
||||
std::array<GLsizeiptr, max_binds> bind_sizes;
|
||||
|
||||
ASSERT_MSG(entries.size() <= max_binds, "Exceeded expected number of binding points.");
|
||||
|
||||
// Upload only the enabled buffers from the 16 constbuffers of each shader stage
|
||||
for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
|
||||
const auto& used_buffer = entries[bindpoint];
|
||||
const auto& buffer = shader_stage.const_buffers[used_buffer.GetIndex()];
|
||||
|
||||
if (!buffer.enabled) {
|
||||
// Set values to zero to unbind buffers
|
||||
bind_ubo_pushbuffer.Push(0, 0, 0);
|
||||
// With disabled buffers set values as zero to unbind them
|
||||
bind_buffers[bindpoint] = 0;
|
||||
bind_offsets[bindpoint] = 0;
|
||||
bind_sizes[bindpoint] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -942,19 +944,30 @@ void RasterizerOpenGL::SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::Shader
|
||||
const GLintptr const_buffer_offset = buffer_cache.UploadMemory(
|
||||
buffer.address, size, static_cast<std::size_t>(uniform_buffer_alignment));
|
||||
|
||||
bind_ubo_pushbuffer.Push(buffer_cache.GetHandle(), const_buffer_offset, size);
|
||||
// Prepare values for multibind
|
||||
bind_buffers[bindpoint] = buffer_cache.GetHandle();
|
||||
bind_offsets[bindpoint] = const_buffer_offset;
|
||||
bind_sizes[bindpoint] = size;
|
||||
}
|
||||
|
||||
// The first binding is reserved for emulation values
|
||||
const GLuint ubo_base_binding = base_bindings.cbuf + 1;
|
||||
glBindBuffersRange(GL_UNIFORM_BUFFER, ubo_base_binding, static_cast<GLsizei>(entries.size()),
|
||||
bind_buffers.data(), bind_offsets.data(), bind_sizes.data());
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SetupGlobalRegions(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage,
|
||||
const Shader& shader, GLenum primitive_mode,
|
||||
BaseBindings base_bindings) {
|
||||
// TODO(Rodrigo): Use ARB_multi_bind here
|
||||
const auto& entries = shader->GetShaderEntries().global_memory_entries;
|
||||
for (std::size_t bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
|
||||
const auto& entry{entries[bindpoint]};
|
||||
const auto& region{global_cache.GetGlobalRegion(entry, stage)};
|
||||
bind_ssbo_pushbuffer.Push(region->GetBufferHandle(), 0,
|
||||
static_cast<GLsizeiptr>(region->GetSizeInBytes()));
|
||||
|
||||
for (u32 bindpoint = 0; bindpoint < static_cast<u32>(entries.size()); ++bindpoint) {
|
||||
const auto& entry = entries[bindpoint];
|
||||
const u32 current_bindpoint = base_bindings.gmem + bindpoint;
|
||||
const auto& region = global_cache.GetGlobalRegion(entry, stage);
|
||||
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, current_bindpoint, region->GetBufferHandle());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -970,7 +983,15 @@ void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& s
|
||||
|
||||
for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
|
||||
const auto& entry = entries[bindpoint];
|
||||
const auto texture = maxwell3d.GetStageTexture(stage, entry.GetOffset());
|
||||
Tegra::Texture::FullTextureInfo texture;
|
||||
if (entry.IsBindless()) {
|
||||
const auto cbuf = entry.GetBindlessCBuf();
|
||||
Tegra::Texture::TextureHandle tex_handle;
|
||||
tex_handle.raw = maxwell3d.AccessConstBuffer32(stage, cbuf.first, cbuf.second);
|
||||
texture = maxwell3d.GetTextureInfo(tex_handle, entry.GetOffset());
|
||||
} else {
|
||||
texture = maxwell3d.GetStageTexture(stage, entry.GetOffset());
|
||||
}
|
||||
const u32 current_bindpoint = base_bindings.sampler + bindpoint;
|
||||
|
||||
texture_samplers[current_bindpoint].SyncWithConfig(texture.tsc);
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include "video_core/renderer_opengl/gl_shader_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
#include "video_core/renderer_opengl/utils.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
@@ -230,9 +229,6 @@ private:
|
||||
PrimitiveAssembler primitive_assembler{buffer_cache};
|
||||
GLint uniform_buffer_alignment;
|
||||
|
||||
BindBuffersRangePushBuffer bind_ubo_pushbuffer{GL_UNIFORM_BUFFER};
|
||||
BindBuffersRangePushBuffer bind_ssbo_pushbuffer{GL_SHADER_STORAGE_BUFFER};
|
||||
|
||||
std::size_t CalculateVertexArraysSize() const;
|
||||
|
||||
std::size_t CalculateIndexBufferSize() const;
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/settings.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/morton.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
|
||||
@@ -112,26 +111,11 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only,
|
||||
params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(),
|
||||
params.srgb_conversion);
|
||||
|
||||
if (config.tsc.depth_compare_enabled) {
|
||||
if (params.pixel_format == PixelFormat::R16U && config.tsc.depth_compare_enabled) {
|
||||
// Some titles create a 'R16U' (normalized 16-bit) texture with depth_compare enabled,
|
||||
// then attempt to sample from it via a shadow sampler. Convert format to Z16 (which also
|
||||
// causes GetFormatType to properly return 'Depth' below).
|
||||
if (GetFormatType(params.pixel_format) == SurfaceType::ColorTexture) {
|
||||
switch (params.pixel_format) {
|
||||
case PixelFormat::R16S:
|
||||
case PixelFormat::R16U:
|
||||
case PixelFormat::R16F:
|
||||
params.pixel_format = PixelFormat::Z16;
|
||||
break;
|
||||
case PixelFormat::R32F:
|
||||
params.pixel_format = PixelFormat::Z32F;
|
||||
break;
|
||||
default:
|
||||
LOG_WARNING(HW_GPU, "Color texture format being used with depth compare: {}",
|
||||
static_cast<u32>(params.pixel_format));
|
||||
break;
|
||||
}
|
||||
}
|
||||
params.pixel_format = PixelFormat::Z16;
|
||||
}
|
||||
|
||||
params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value());
|
||||
@@ -281,10 +265,6 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only,
|
||||
params.component_type = ComponentTypeFromRenderTarget(config.format);
|
||||
params.type = GetFormatType(params.pixel_format);
|
||||
params.width = config.width;
|
||||
if (!params.is_tiled) {
|
||||
const u32 bpp = params.GetFormatBpp() / 8;
|
||||
params.pitch = config.width * bpp;
|
||||
}
|
||||
params.height = config.height;
|
||||
params.unaligned_height = config.height;
|
||||
params.target = SurfaceTarget::Texture2D;
|
||||
@@ -681,8 +661,8 @@ void CachedSurface::FlushGLBuffer() {
|
||||
gl_buffer[0].resize(GetSizeInBytes());
|
||||
|
||||
const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
|
||||
const u32 align = std::clamp(params.RowAlign(0), 1U, 8U);
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, align);
|
||||
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT
|
||||
ASSERT(params.width * GetBytesPerPixel(params.pixel_format) % 4 == 0);
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.width));
|
||||
ASSERT(!tuple.compressed);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
@@ -727,8 +707,8 @@ void CachedSurface::UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle,
|
||||
|
||||
const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
|
||||
|
||||
const u32 align = std::clamp(params.RowAlign(mip_map), 1U, 8U);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, align);
|
||||
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT
|
||||
ASSERT(params.MipWidth(mip_map) * GetBytesPerPixel(params.pixel_format) % 4 == 0);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(params.MipWidth(mip_map)));
|
||||
|
||||
const auto image_size = static_cast<GLsizei>(params.GetMipmapSizeGL(mip_map, false));
|
||||
@@ -1194,16 +1174,10 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
|
||||
return new_surface;
|
||||
}
|
||||
|
||||
const bool old_compressed =
|
||||
GetFormatTuple(old_params.pixel_format, old_params.component_type).compressed;
|
||||
const bool new_compressed =
|
||||
GetFormatTuple(new_params.pixel_format, new_params.component_type).compressed;
|
||||
const bool compatible_formats =
|
||||
GetFormatBpp(old_params.pixel_format) == GetFormatBpp(new_params.pixel_format) &&
|
||||
!(old_compressed || new_compressed);
|
||||
// For compatible surfaces, we can just do fast glCopyImageSubData based copy
|
||||
if (old_params.target == new_params.target && old_params.depth == new_params.depth &&
|
||||
old_params.depth == 1 && compatible_formats) {
|
||||
if (old_params.target == new_params.target && old_params.type == new_params.type &&
|
||||
old_params.depth == new_params.depth && old_params.depth == 1 &&
|
||||
GetFormatBpp(old_params.pixel_format) == GetFormatBpp(new_params.pixel_format)) {
|
||||
FastCopySurface(old_surface, new_surface);
|
||||
return new_surface;
|
||||
}
|
||||
@@ -1218,7 +1192,7 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
|
||||
case SurfaceTarget::TextureCubemap:
|
||||
case SurfaceTarget::Texture2DArray:
|
||||
case SurfaceTarget::TextureCubeArray:
|
||||
if (compatible_formats)
|
||||
if (old_params.pixel_format == new_params.pixel_format)
|
||||
FastLayeredCopySurface(old_surface, new_surface);
|
||||
else {
|
||||
AccurateCopySurface(old_surface, new_surface);
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/bit_util.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/hash.h"
|
||||
#include "common/math_util.h"
|
||||
@@ -206,13 +205,6 @@ struct SurfaceParams {
|
||||
return bd;
|
||||
}
|
||||
|
||||
u32 RowAlign(u32 mip_level) const {
|
||||
const u32 m_width = MipWidth(mip_level);
|
||||
const u32 bytes_per_pixel = GetBytesPerPixel(pixel_format);
|
||||
const u32 l2 = Common::CountTrailingZeroes32(m_width * bytes_per_pixel);
|
||||
return (1U << l2);
|
||||
}
|
||||
|
||||
/// Creates SurfaceParams from a texture configuration
|
||||
static SurfaceParams CreateForTexture(const Tegra::Texture::FullTextureInfo& config,
|
||||
const GLShader::SamplerEntry& entry);
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "common/hash.h"
|
||||
#include "core/core.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
|
||||
|
||||
@@ -552,7 +552,8 @@ private:
|
||||
} else if (std::holds_alternative<OperationNode>(*offset)) {
|
||||
// Indirect access
|
||||
const std::string final_offset = code.GenerateTemporary();
|
||||
code.AddLine("uint " + final_offset + " = (ftou(" + Visit(offset) + ") / 4);");
|
||||
code.AddLine("uint " + final_offset + " = (ftou(" + Visit(offset) + ") / 4) & " +
|
||||
std::to_string(MAX_CONSTBUFFER_ELEMENTS - 1) + ';');
|
||||
return fmt::format("{}[{} / 4][{} % 4]", GetConstBuffer(cbuf->GetIndex()),
|
||||
final_offset, final_offset);
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ private:
|
||||
struct ShaderEntries {
|
||||
std::vector<ConstBufferEntry> const_buffers;
|
||||
std::vector<SamplerEntry> samplers;
|
||||
std::vector<SamplerEntry> bindless_samplers;
|
||||
std::vector<GlobalMemoryEntry> global_memory_entries;
|
||||
std::array<bool, Maxwell::NumClipDistances> clip_distances{};
|
||||
std::size_t shader_length{};
|
||||
@@ -68,4 +69,4 @@ std::string GetCommonDeclarations();
|
||||
ProgramResult Decompile(const VideoCommon::Shader::ShaderIR& ir, Maxwell::ShaderStage stage,
|
||||
const std::string& suffix);
|
||||
|
||||
} // namespace OpenGL::GLShader
|
||||
} // namespace OpenGL::GLShader
|
||||
|
||||
@@ -319,16 +319,19 @@ std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEn
|
||||
u32 type{};
|
||||
u8 is_array{};
|
||||
u8 is_shadow{};
|
||||
u8 is_bindless{};
|
||||
if (file.ReadBytes(&offset, sizeof(u64)) != sizeof(u64) ||
|
||||
file.ReadBytes(&index, sizeof(u64)) != sizeof(u64) ||
|
||||
file.ReadBytes(&type, sizeof(u32)) != sizeof(u32) ||
|
||||
file.ReadBytes(&is_array, sizeof(u8)) != sizeof(u8) ||
|
||||
file.ReadBytes(&is_shadow, sizeof(u8)) != sizeof(u8)) {
|
||||
file.ReadBytes(&is_shadow, sizeof(u8)) != sizeof(u8) ||
|
||||
file.ReadBytes(&is_bindless, sizeof(u8)) != sizeof(u8)) {
|
||||
return {};
|
||||
}
|
||||
entry.entries.samplers.emplace_back(
|
||||
static_cast<std::size_t>(offset), static_cast<std::size_t>(index),
|
||||
static_cast<Tegra::Shader::TextureType>(type), is_array != 0, is_shadow != 0);
|
||||
entry.entries.samplers.emplace_back(static_cast<std::size_t>(offset),
|
||||
static_cast<std::size_t>(index),
|
||||
static_cast<Tegra::Shader::TextureType>(type),
|
||||
is_array != 0, is_shadow != 0, is_bindless != 0);
|
||||
}
|
||||
|
||||
u32 global_memory_count{};
|
||||
@@ -388,7 +391,8 @@ bool ShaderDiskCacheOpenGL::SaveDecompiledFile(FileUtil::IOFile& file, u64 uniqu
|
||||
file.WriteObject(static_cast<u64>(sampler.GetIndex())) != 1 ||
|
||||
file.WriteObject(static_cast<u32>(sampler.GetType())) != 1 ||
|
||||
file.WriteObject(static_cast<u8>(sampler.IsArray() ? 1 : 0)) != 1 ||
|
||||
file.WriteObject(static_cast<u8>(sampler.IsShadow() ? 1 : 0)) != 1) {
|
||||
file.WriteObject(static_cast<u8>(sampler.IsShadow() ? 1 : 0)) != 1 ||
|
||||
file.WriteObject(static_cast<u8>(sampler.IsBindless() ? 1 : 0)) != 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,44 +2,12 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_manager.h"
|
||||
|
||||
namespace OpenGL::GLShader {
|
||||
|
||||
using Tegra::Engines::Maxwell3D;
|
||||
|
||||
ProgramManager::ProgramManager() {
|
||||
pipeline.Create();
|
||||
}
|
||||
|
||||
ProgramManager::~ProgramManager() = default;
|
||||
|
||||
void ProgramManager::ApplyTo(OpenGLState& state) {
|
||||
UpdatePipeline();
|
||||
state.draw.shader_program = 0;
|
||||
state.draw.program_pipeline = pipeline.handle;
|
||||
}
|
||||
|
||||
void ProgramManager::UpdatePipeline() {
|
||||
// Avoid updating the pipeline when values have no changed
|
||||
if (old_state == current_state) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Workaround for AMD bug
|
||||
constexpr GLenum all_used_stages{GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT |
|
||||
GL_FRAGMENT_SHADER_BIT};
|
||||
glUseProgramStages(pipeline.handle, all_used_stages, 0);
|
||||
|
||||
glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, current_state.vertex_shader);
|
||||
glUseProgramStages(pipeline.handle, GL_GEOMETRY_SHADER_BIT, current_state.geometry_shader);
|
||||
glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, current_state.fragment_shader);
|
||||
|
||||
old_state = current_state;
|
||||
}
|
||||
|
||||
void MaxwellUniformData::SetFromRegs(const Maxwell3D& maxwell, std::size_t shader_stage) {
|
||||
const auto& regs = maxwell.regs;
|
||||
const auto& state = maxwell.state;
|
||||
@@ -48,7 +16,7 @@ void MaxwellUniformData::SetFromRegs(const Maxwell3D& maxwell, std::size_t shade
|
||||
viewport_flip[0] = regs.viewport_transform[0].scale_x < 0.0 ? -1.0f : 1.0f;
|
||||
viewport_flip[1] = regs.viewport_transform[0].scale_y < 0.0 ? -1.0f : 1.0f;
|
||||
|
||||
auto func{static_cast<u32>(regs.alpha_test_func)};
|
||||
u32 func = static_cast<u32>(regs.alpha_test_func);
|
||||
// Normalize the gl variants of opCompare to be the same as the normal variants
|
||||
const u32 op_gl_variant_base = static_cast<u32>(Maxwell3D::Regs::ComparisonOp::Never);
|
||||
if (func >= op_gl_variant_base) {
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
@@ -40,48 +38,55 @@ static_assert(sizeof(MaxwellUniformData) < 16384,
|
||||
|
||||
class ProgramManager {
|
||||
public:
|
||||
explicit ProgramManager();
|
||||
~ProgramManager();
|
||||
|
||||
void ApplyTo(OpenGLState& state);
|
||||
ProgramManager() {
|
||||
pipeline.Create();
|
||||
}
|
||||
|
||||
void UseProgrammableVertexShader(GLuint program) {
|
||||
current_state.vertex_shader = program;
|
||||
vs = program;
|
||||
}
|
||||
|
||||
void UseProgrammableGeometryShader(GLuint program) {
|
||||
current_state.geometry_shader = program;
|
||||
gs = program;
|
||||
}
|
||||
|
||||
void UseProgrammableFragmentShader(GLuint program) {
|
||||
current_state.fragment_shader = program;
|
||||
fs = program;
|
||||
}
|
||||
|
||||
void UseTrivialGeometryShader() {
|
||||
current_state.geometry_shader = 0;
|
||||
gs = 0;
|
||||
}
|
||||
|
||||
void ApplyTo(OpenGLState& state) {
|
||||
UpdatePipeline();
|
||||
state.draw.shader_program = 0;
|
||||
state.draw.program_pipeline = pipeline.handle;
|
||||
}
|
||||
|
||||
private:
|
||||
struct PipelineState {
|
||||
bool operator==(const PipelineState& rhs) const {
|
||||
return vertex_shader == rhs.vertex_shader && fragment_shader == rhs.fragment_shader &&
|
||||
geometry_shader == rhs.geometry_shader;
|
||||
}
|
||||
void UpdatePipeline() {
|
||||
// Avoid updating the pipeline when values have no changed
|
||||
if (old_vs == vs && old_fs == fs && old_gs == gs)
|
||||
return;
|
||||
// Workaround for AMD bug
|
||||
glUseProgramStages(pipeline.handle,
|
||||
GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | GL_FRAGMENT_SHADER_BIT,
|
||||
0);
|
||||
|
||||
bool operator!=(const PipelineState& rhs) const {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, vs);
|
||||
glUseProgramStages(pipeline.handle, GL_GEOMETRY_SHADER_BIT, gs);
|
||||
glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, fs);
|
||||
|
||||
GLuint vertex_shader{};
|
||||
GLuint fragment_shader{};
|
||||
GLuint geometry_shader{};
|
||||
};
|
||||
|
||||
void UpdatePipeline();
|
||||
// Update the old values
|
||||
old_vs = vs;
|
||||
old_fs = fs;
|
||||
old_gs = gs;
|
||||
}
|
||||
|
||||
OGLPipeline pipeline;
|
||||
PipelineState current_state;
|
||||
PipelineState old_state;
|
||||
GLuint vs{}, fs{}, gs{};
|
||||
GLuint old_vs{}, old_fs{}, old_gs{};
|
||||
};
|
||||
|
||||
} // namespace OpenGL::GLShader
|
||||
|
||||
@@ -5,39 +5,11 @@
|
||||
#include <string>
|
||||
#include <fmt/format.h>
|
||||
#include <glad/glad.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_opengl/utils.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
BindBuffersRangePushBuffer::BindBuffersRangePushBuffer(GLenum target) : target{target} {}
|
||||
|
||||
BindBuffersRangePushBuffer::~BindBuffersRangePushBuffer() = default;
|
||||
|
||||
void BindBuffersRangePushBuffer::Setup(GLuint first_) {
|
||||
first = first_;
|
||||
buffers.clear();
|
||||
offsets.clear();
|
||||
sizes.clear();
|
||||
}
|
||||
|
||||
void BindBuffersRangePushBuffer::Push(GLuint buffer, GLintptr offset, GLsizeiptr size) {
|
||||
buffers.push_back(buffer);
|
||||
offsets.push_back(offset);
|
||||
sizes.push_back(size);
|
||||
}
|
||||
|
||||
void BindBuffersRangePushBuffer::Bind() const {
|
||||
const std::size_t count{buffers.size()};
|
||||
DEBUG_ASSERT(count == offsets.size() && count == sizes.size());
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
glBindBuffersRange(target, first, static_cast<GLsizei>(count), buffers.data(), offsets.data(),
|
||||
sizes.data());
|
||||
}
|
||||
|
||||
void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string extra_info) {
|
||||
if (!GLAD_GL_KHR_debug) {
|
||||
return; // We don't need to throw an error as this is just for debugging
|
||||
|
||||
@@ -5,31 +5,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <glad/glad.h>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
class BindBuffersRangePushBuffer {
|
||||
public:
|
||||
BindBuffersRangePushBuffer(GLenum target);
|
||||
~BindBuffersRangePushBuffer();
|
||||
|
||||
void Setup(GLuint first_);
|
||||
|
||||
void Push(GLuint buffer, GLintptr offset, GLsizeiptr size);
|
||||
|
||||
void Bind() const;
|
||||
|
||||
private:
|
||||
GLenum target;
|
||||
GLuint first;
|
||||
std::vector<GLuint> buffers;
|
||||
std::vector<GLintptr> offsets;
|
||||
std::vector<GLsizeiptr> sizes;
|
||||
};
|
||||
|
||||
void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string extra_info = "");
|
||||
|
||||
} // namespace OpenGL
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,80 +0,0 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <sirit/sirit.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/shader/shader_ir.h"
|
||||
|
||||
namespace VideoCommon::Shader {
|
||||
class ShaderIR;
|
||||
}
|
||||
|
||||
namespace Vulkan::VKShader {
|
||||
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
|
||||
using SamplerEntry = VideoCommon::Shader::Sampler;
|
||||
|
||||
constexpr u32 DESCRIPTOR_SET = 0;
|
||||
|
||||
class ConstBufferEntry : public VideoCommon::Shader::ConstBuffer {
|
||||
public:
|
||||
explicit constexpr ConstBufferEntry(const VideoCommon::Shader::ConstBuffer& entry, u32 index)
|
||||
: VideoCommon::Shader::ConstBuffer{entry}, index{index} {}
|
||||
|
||||
constexpr u32 GetIndex() const {
|
||||
return index;
|
||||
}
|
||||
|
||||
private:
|
||||
u32 index{};
|
||||
};
|
||||
|
||||
class GlobalBufferEntry {
|
||||
public:
|
||||
explicit GlobalBufferEntry(u32 cbuf_index, u32 cbuf_offset)
|
||||
: cbuf_index{cbuf_index}, cbuf_offset{cbuf_offset} {}
|
||||
|
||||
u32 GetCbufIndex() const {
|
||||
return cbuf_index;
|
||||
}
|
||||
|
||||
u32 GetCbufOffset() const {
|
||||
return cbuf_offset;
|
||||
}
|
||||
|
||||
private:
|
||||
u32 cbuf_index{};
|
||||
u32 cbuf_offset{};
|
||||
};
|
||||
|
||||
struct ShaderEntries {
|
||||
u32 const_buffers_base_binding{};
|
||||
u32 global_buffers_base_binding{};
|
||||
u32 samplers_base_binding{};
|
||||
std::vector<ConstBufferEntry> const_buffers;
|
||||
std::vector<GlobalBufferEntry> global_buffers;
|
||||
std::vector<SamplerEntry> samplers;
|
||||
std::set<u32> attributes;
|
||||
std::array<bool, Maxwell::NumClipDistances> clip_distances{};
|
||||
std::size_t shader_length{};
|
||||
Sirit::Id entry_function{};
|
||||
std::vector<Sirit::Id> interfaces;
|
||||
};
|
||||
|
||||
using DecompilerResult = std::pair<std::unique_ptr<Sirit::Module>, ShaderEntries>;
|
||||
|
||||
DecompilerResult Decompile(const VideoCommon::Shader::ShaderIR& ir, Maxwell::ShaderStage stage);
|
||||
|
||||
} // namespace Vulkan::VKShader
|
||||
@@ -40,7 +40,7 @@ static std::size_t GetCoordCount(TextureType texture_type) {
|
||||
u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
|
||||
const Instruction instr = {program_code[pc]};
|
||||
const auto opcode = OpCode::Decode(instr);
|
||||
|
||||
bool is_bindless = false;
|
||||
switch (opcode->get().GetId()) {
|
||||
case OpCode::Id::TEX: {
|
||||
if (instr.tex.UsesMiscMode(TextureMiscMode::NODEP)) {
|
||||
@@ -54,7 +54,25 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
|
||||
const auto process_mode = instr.tex.GetTextureProcessMode();
|
||||
WriteTexInstructionFloat(
|
||||
bb, instr,
|
||||
GetTexCode(instr, texture_type, process_mode, depth_compare, is_array, is_aoffi));
|
||||
GetTexCode(instr, texture_type, process_mode, depth_compare, is_array, is_aoffi, {}));
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TEX_B: {
|
||||
UNIMPLEMENTED_IF_MSG(instr.tex.UsesMiscMode(TextureMiscMode::AOFFI),
|
||||
"AOFFI is not implemented");
|
||||
|
||||
if (instr.tex.UsesMiscMode(TextureMiscMode::NODEP)) {
|
||||
LOG_WARNING(HW_GPU, "TEX.NODEP implementation is incomplete");
|
||||
}
|
||||
|
||||
const TextureType texture_type{instr.tex_b.texture_type};
|
||||
const bool is_array = instr.tex_b.array != 0;
|
||||
const bool is_aoffi = instr.tex.UsesMiscMode(TextureMiscMode::AOFFI);
|
||||
const bool depth_compare = instr.tex_b.UsesMiscMode(TextureMiscMode::DC);
|
||||
const auto process_mode = instr.tex_b.GetTextureProcessMode();
|
||||
WriteTexInstructionFloat(bb, instr,
|
||||
GetTexCode(instr, texture_type, process_mode, depth_compare,
|
||||
is_array, is_aoffi, {instr.gpr20}));
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TEXS: {
|
||||
@@ -134,6 +152,9 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
|
||||
WriteTexsInstructionFloat(bb, instr, values);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TXQ_B:
|
||||
is_bindless = true;
|
||||
[[fallthrough]];
|
||||
case OpCode::Id::TXQ: {
|
||||
if (instr.txq.UsesMiscMode(TextureMiscMode::NODEP)) {
|
||||
LOG_WARNING(HW_GPU, "TXQ.NODEP implementation is incomplete");
|
||||
@@ -143,7 +164,10 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
|
||||
// Sadly, not all texture instructions specify the type of texture their sampler
|
||||
// uses. This must be fixed at a later instance.
|
||||
const auto& sampler =
|
||||
GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false, false);
|
||||
is_bindless
|
||||
? GetBindlessSampler(instr.gpr8, Tegra::Shader::TextureType::Texture2D, false,
|
||||
false)
|
||||
: GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false, false);
|
||||
|
||||
u32 indexer = 0;
|
||||
switch (instr.txq.query_type) {
|
||||
@@ -154,7 +178,8 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
|
||||
}
|
||||
MetaTexture meta{sampler, {}, {}, {}, {}, {}, {}, element};
|
||||
const Node value =
|
||||
Operation(OperationCode::TextureQueryDimensions, meta, GetRegister(instr.gpr8));
|
||||
Operation(OperationCode::TextureQueryDimensions, meta,
|
||||
GetRegister(instr.gpr8.Value() + (is_bindless ? 1 : 0)));
|
||||
SetTemporal(bb, indexer++, value);
|
||||
}
|
||||
for (u32 i = 0; i < indexer; ++i) {
|
||||
@@ -168,6 +193,9 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TMML_B:
|
||||
is_bindless = true;
|
||||
[[fallthrough]];
|
||||
case OpCode::Id::TMML: {
|
||||
UNIMPLEMENTED_IF_MSG(instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV),
|
||||
"NDV is not implemented");
|
||||
@@ -178,7 +206,9 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
|
||||
|
||||
auto texture_type = instr.tmml.texture_type.Value();
|
||||
const bool is_array = instr.tmml.array != 0;
|
||||
const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, false);
|
||||
const auto& sampler = is_bindless
|
||||
? GetBindlessSampler(instr.gpr20, texture_type, is_array, false)
|
||||
: GetSampler(instr.sampler, texture_type, is_array, false);
|
||||
|
||||
std::vector<Node> coords;
|
||||
|
||||
@@ -199,17 +229,19 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
|
||||
coords.push_back(GetRegister(instr.gpr8.Value() + 1));
|
||||
texture_type = TextureType::Texture2D;
|
||||
}
|
||||
|
||||
u32 indexer = 0;
|
||||
for (u32 element = 0; element < 2; ++element) {
|
||||
if (!instr.tmml.IsComponentEnabled(element)) {
|
||||
continue;
|
||||
}
|
||||
auto params = coords;
|
||||
MetaTexture meta{sampler, {}, {}, {}, {}, {}, {}, element};
|
||||
const Node value = Operation(OperationCode::TextureQueryLod, meta, std::move(params));
|
||||
SetTemporal(bb, element, value);
|
||||
SetTemporal(bb, indexer++, value);
|
||||
}
|
||||
for (u32 element = 0; element < 2; ++element) {
|
||||
SetRegister(bb, instr.gpr0.Value() + element, GetTemporal(element));
|
||||
for (u32 i = 0; i < indexer; ++i) {
|
||||
SetRegister(bb, instr.gpr0.Value() + i, GetTemporal(i));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TLDS: {
|
||||
@@ -254,6 +286,34 @@ const Sampler& ShaderIR::GetSampler(const Tegra::Shader::Sampler& sampler, Textu
|
||||
return *used_samplers.emplace(entry).first;
|
||||
}
|
||||
|
||||
const Sampler& ShaderIR::GetBindlessSampler(const Tegra::Shader::Register& reg, TextureType type,
|
||||
bool is_array, bool is_shadow) {
|
||||
const Node sampler_register = GetRegister(reg);
|
||||
const Node base_sampler =
|
||||
TrackCbuf(sampler_register, global_code, static_cast<s64>(global_code.size()));
|
||||
const auto cbuf = std::get_if<CbufNode>(base_sampler);
|
||||
const auto cbuf_offset_imm = std::get_if<ImmediateNode>(cbuf->GetOffset());
|
||||
ASSERT(cbuf_offset_imm != nullptr);
|
||||
const auto cbuf_offset = cbuf_offset_imm->GetValue();
|
||||
const auto cbuf_index = cbuf->GetIndex();
|
||||
const u64 cbuf_key = (cbuf_index << 32) | cbuf_offset;
|
||||
|
||||
// If this sampler has already been used, return the existing mapping.
|
||||
const auto itr =
|
||||
std::find_if(used_samplers.begin(), used_samplers.end(),
|
||||
[&](const Sampler& entry) { return entry.GetOffset() == cbuf_key; });
|
||||
if (itr != used_samplers.end()) {
|
||||
ASSERT(itr->GetType() == type && itr->IsArray() == is_array &&
|
||||
itr->IsShadow() == is_shadow);
|
||||
return *itr;
|
||||
}
|
||||
|
||||
// Otherwise create a new mapping for this sampler
|
||||
const std::size_t next_index = used_samplers.size();
|
||||
const Sampler entry{cbuf_index, cbuf_offset, next_index, type, is_array, is_shadow};
|
||||
return *used_samplers.emplace(entry).first;
|
||||
}
|
||||
|
||||
void ShaderIR::WriteTexInstructionFloat(NodeBlock& bb, Instruction instr, const Node4& components) {
|
||||
u32 dest_elem = 0;
|
||||
for (u32 elem = 0; elem < 4; ++elem) {
|
||||
@@ -326,22 +386,27 @@ void ShaderIR::WriteTexsInstructionHalfFloat(NodeBlock& bb, Instruction instr,
|
||||
Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
|
||||
TextureProcessMode process_mode, std::vector<Node> coords,
|
||||
Node array, Node depth_compare, u32 bias_offset,
|
||||
std::vector<Node> aoffi) {
|
||||
std::vector<Node> aoffi,
|
||||
std::optional<Tegra::Shader::Register> bindless_reg) {
|
||||
const bool is_array = array;
|
||||
const bool is_shadow = depth_compare;
|
||||
const bool is_bindless = bindless_reg.has_value();
|
||||
|
||||
UNIMPLEMENTED_IF_MSG((texture_type == TextureType::Texture3D && (is_array || is_shadow)) ||
|
||||
(texture_type == TextureType::TextureCube && is_array && is_shadow),
|
||||
"This method is not supported.");
|
||||
|
||||
const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, is_shadow);
|
||||
const auto& sampler = is_bindless
|
||||
? GetBindlessSampler(*bindless_reg, texture_type, is_array, is_shadow)
|
||||
: GetSampler(instr.sampler, texture_type, is_array, is_shadow);
|
||||
|
||||
const bool lod_needed = process_mode == TextureProcessMode::LZ ||
|
||||
process_mode == TextureProcessMode::LL ||
|
||||
process_mode == TextureProcessMode::LLA;
|
||||
|
||||
// LOD selection (either via bias or explicit textureLod) not supported in GL for
|
||||
// sampler2DArrayShadow and samplerCubeArrayShadow.
|
||||
// LOD selection (either via bias or explicit textureLod) not
|
||||
// supported in GL for sampler2DArrayShadow and
|
||||
// samplerCubeArrayShadow.
|
||||
const bool gl_lod_supported =
|
||||
!((texture_type == Tegra::Shader::TextureType::Texture2D && is_array && is_shadow) ||
|
||||
(texture_type == Tegra::Shader::TextureType::TextureCube && is_array && is_shadow));
|
||||
@@ -359,8 +424,9 @@ Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
|
||||
lod = Immediate(0.0f);
|
||||
break;
|
||||
case TextureProcessMode::LB:
|
||||
// If present, lod or bias are always stored in the register indexed by the gpr20
|
||||
// field with an offset depending on the usage of the other registers
|
||||
// If present, lod or bias are always stored in the register
|
||||
// indexed by the gpr20 field with an offset depending on the
|
||||
// usage of the other registers
|
||||
bias = GetRegister(instr.gpr20.Value() + bias_offset);
|
||||
break;
|
||||
case TextureProcessMode::LL:
|
||||
@@ -384,11 +450,18 @@ Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
|
||||
|
||||
Node4 ShaderIR::GetTexCode(Instruction instr, TextureType texture_type,
|
||||
TextureProcessMode process_mode, bool depth_compare, bool is_array,
|
||||
bool is_aoffi) {
|
||||
bool is_aoffi, std::optional<Tegra::Shader::Register> bindless_reg) {
|
||||
const bool lod_bias_enabled{
|
||||
(process_mode != TextureProcessMode::None && process_mode != TextureProcessMode::LZ)};
|
||||
|
||||
const bool is_bindless = bindless_reg.has_value();
|
||||
|
||||
u64 parameter_register = instr.gpr20.Value();
|
||||
if (is_bindless) {
|
||||
++parameter_register;
|
||||
}
|
||||
|
||||
const u32 bias_lod_offset = (is_bindless ? 1 : 0);
|
||||
if (lod_bias_enabled) {
|
||||
++parameter_register;
|
||||
}
|
||||
@@ -423,7 +496,8 @@ Node4 ShaderIR::GetTexCode(Instruction instr, TextureType texture_type,
|
||||
dc = GetRegister(parameter_register++);
|
||||
}
|
||||
|
||||
return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, 0, aoffi);
|
||||
return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, bias_lod_offset,
|
||||
aoffi, bindless_reg);
|
||||
}
|
||||
|
||||
Node4 ShaderIR::GetTexsCode(Instruction instr, TextureType texture_type,
|
||||
@@ -459,7 +533,8 @@ Node4 ShaderIR::GetTexsCode(Instruction instr, TextureType texture_type,
|
||||
dc = GetRegister(depth_register);
|
||||
}
|
||||
|
||||
return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, bias_offset, {});
|
||||
return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, bias_offset, {},
|
||||
{});
|
||||
}
|
||||
|
||||
Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool depth_compare,
|
||||
|
||||
@@ -29,55 +29,39 @@ u32 ShaderIR::DecodeXmad(NodeBlock& bb, u32 pc) {
|
||||
const bool is_signed_b = instr.xmad.sign_b == 1;
|
||||
const bool is_signed_c = is_signed_a;
|
||||
|
||||
auto [is_merge, is_psl, is_high_b, mode, op_b,
|
||||
op_c] = [&]() -> std::tuple<bool, bool, bool, Tegra::Shader::XmadMode, Node, Node> {
|
||||
auto [is_merge, op_b, op_c] = [&]() -> std::tuple<bool, Node, Node> {
|
||||
switch (opcode->get().GetId()) {
|
||||
case OpCode::Id::XMAD_CR:
|
||||
return {instr.xmad.merge_56,
|
||||
instr.xmad.product_shift_left_second,
|
||||
instr.xmad.high_b,
|
||||
instr.xmad.mode_cbf,
|
||||
GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset()),
|
||||
GetRegister(instr.gpr39)};
|
||||
case OpCode::Id::XMAD_RR:
|
||||
return {instr.xmad.merge_37, instr.xmad.product_shift_left, instr.xmad.high_b_rr,
|
||||
instr.xmad.mode, GetRegister(instr.gpr20), GetRegister(instr.gpr39)};
|
||||
return {instr.xmad.merge_37, GetRegister(instr.gpr20), GetRegister(instr.gpr39)};
|
||||
case OpCode::Id::XMAD_RC:
|
||||
return {false,
|
||||
false,
|
||||
instr.xmad.high_b,
|
||||
instr.xmad.mode_cbf,
|
||||
GetRegister(instr.gpr39),
|
||||
return {false, GetRegister(instr.gpr39),
|
||||
GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset())};
|
||||
case OpCode::Id::XMAD_IMM:
|
||||
return {instr.xmad.merge_37,
|
||||
instr.xmad.product_shift_left,
|
||||
false,
|
||||
instr.xmad.mode,
|
||||
Immediate(static_cast<u32>(instr.xmad.imm20_16)),
|
||||
return {instr.xmad.merge_37, Immediate(static_cast<u32>(instr.xmad.imm20_16)),
|
||||
GetRegister(instr.gpr39)};
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unhandled XMAD instruction: {}", opcode->get().GetName());
|
||||
return {false, false, false, Tegra::Shader::XmadMode::None, Immediate(0), Immediate(0)};
|
||||
return {false, Immediate(0), Immediate(0)};
|
||||
}();
|
||||
|
||||
op_a = BitfieldExtract(op_a, instr.xmad.high_a ? 16 : 0, 16);
|
||||
|
||||
const Node original_b = op_b;
|
||||
op_b = BitfieldExtract(op_b, is_high_b ? 16 : 0, 16);
|
||||
op_b = BitfieldExtract(op_b, instr.xmad.high_b ? 16 : 0, 16);
|
||||
|
||||
// TODO(Rodrigo): Use an appropiate sign for this operation
|
||||
Node product = Operation(OperationCode::IMul, NO_PRECISE, op_a, op_b);
|
||||
if (is_psl) {
|
||||
if (instr.xmad.product_shift_left) {
|
||||
product = Operation(OperationCode::ILogicalShiftLeft, NO_PRECISE, product, Immediate(16));
|
||||
}
|
||||
SetTemporal(bb, 0, product);
|
||||
product = GetTemporal(0);
|
||||
|
||||
const Node original_c = op_c;
|
||||
const Tegra::Shader::XmadMode set_mode = mode; // Workaround to clang compile error
|
||||
op_c = [&]() {
|
||||
switch (set_mode) {
|
||||
switch (instr.xmad.mode) {
|
||||
case Tegra::Shader::XmadMode::None:
|
||||
return original_c;
|
||||
case Tegra::Shader::XmadMode::CLo:
|
||||
@@ -96,13 +80,8 @@ u32 ShaderIR::DecodeXmad(NodeBlock& bb, u32 pc) {
|
||||
}
|
||||
}();
|
||||
|
||||
SetTemporal(bb, 1, op_c);
|
||||
op_c = GetTemporal(1);
|
||||
|
||||
// TODO(Rodrigo): Use an appropiate sign for this operation
|
||||
Node sum = Operation(OperationCode::IAdd, product, op_c);
|
||||
SetTemporal(bb, 2, sum);
|
||||
sum = GetTemporal(2);
|
||||
if (is_merge) {
|
||||
const Node a = BitfieldExtract(sum, 0, 16);
|
||||
const Node b =
|
||||
@@ -116,4 +95,4 @@ u32 ShaderIR::DecodeXmad(NodeBlock& bb, u32 pc) {
|
||||
return pc;
|
||||
}
|
||||
|
||||
} // namespace VideoCommon::Shader
|
||||
} // namespace VideoCommon::Shader
|
||||
@@ -196,9 +196,23 @@ enum class ExitMethod {
|
||||
|
||||
class Sampler {
|
||||
public:
|
||||
// Use this constructor for bounded Samplers
|
||||
explicit Sampler(std::size_t offset, std::size_t index, Tegra::Shader::TextureType type,
|
||||
bool is_array, bool is_shadow)
|
||||
: offset{offset}, index{index}, type{type}, is_array{is_array}, is_shadow{is_shadow} {}
|
||||
: offset{offset}, index{index}, type{type}, is_array{is_array}, is_shadow{is_shadow},
|
||||
is_bindless{false} {}
|
||||
|
||||
// Use this constructor for bindless Samplers
|
||||
explicit Sampler(u32 cbuf_index, u32 cbuf_offset, std::size_t index,
|
||||
Tegra::Shader::TextureType type, bool is_array, bool is_shadow)
|
||||
: offset{(static_cast<u64>(cbuf_index) << 32) | cbuf_offset}, index{index}, type{type},
|
||||
is_array{is_array}, is_shadow{is_shadow}, is_bindless{true} {}
|
||||
|
||||
// Use this only for serialization/deserialization
|
||||
explicit Sampler(std::size_t offset, std::size_t index, Tegra::Shader::TextureType type,
|
||||
bool is_array, bool is_shadow, bool is_bindless)
|
||||
: offset{offset}, index{index}, type{type}, is_array{is_array}, is_shadow{is_shadow},
|
||||
is_bindless{is_bindless} {}
|
||||
|
||||
std::size_t GetOffset() const {
|
||||
return offset;
|
||||
@@ -220,6 +234,14 @@ public:
|
||||
return is_shadow;
|
||||
}
|
||||
|
||||
bool IsBindless() const {
|
||||
return is_bindless;
|
||||
}
|
||||
|
||||
std::pair<u32, u32> GetBindlessCBuf() const {
|
||||
return {static_cast<u32>(offset >> 32), static_cast<u32>(offset)};
|
||||
}
|
||||
|
||||
bool operator<(const Sampler& rhs) const {
|
||||
return std::tie(offset, index, type, is_array, is_shadow) <
|
||||
std::tie(rhs.offset, rhs.index, rhs.type, rhs.is_array, rhs.is_shadow);
|
||||
@@ -231,8 +253,9 @@ private:
|
||||
std::size_t offset{};
|
||||
std::size_t index{}; ///< Value used to index into the generated GLSL sampler array.
|
||||
Tegra::Shader::TextureType type{}; ///< The type used to sample this texture (Texture2D, etc)
|
||||
bool is_array{}; ///< Whether the texture is being sampled as an array texture or not.
|
||||
bool is_shadow{}; ///< Whether the texture is being sampled as a depth texture or not.
|
||||
bool is_array{}; ///< Whether the texture is being sampled as an array texture or not.
|
||||
bool is_shadow{}; ///< Whether the texture is being sampled as a depth texture or not.
|
||||
bool is_bindless{}; ///< Whether this sampler belongs to a bindless texture or not.
|
||||
};
|
||||
|
||||
class ConstBuffer {
|
||||
@@ -730,6 +753,11 @@ private:
|
||||
const Sampler& GetSampler(const Tegra::Shader::Sampler& sampler,
|
||||
Tegra::Shader::TextureType type, bool is_array, bool is_shadow);
|
||||
|
||||
// Accesses a texture sampler for a bindless texture.
|
||||
const Sampler& GetBindlessSampler(const Tegra::Shader::Register& reg,
|
||||
Tegra::Shader::TextureType type, bool is_array,
|
||||
bool is_shadow);
|
||||
|
||||
/// Extracts a sequence of bits from a node
|
||||
Node BitfieldExtract(Node value, u32 offset, u32 bits);
|
||||
|
||||
@@ -743,7 +771,8 @@ private:
|
||||
|
||||
Node4 GetTexCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
|
||||
Tegra::Shader::TextureProcessMode process_mode, bool depth_compare,
|
||||
bool is_array, bool is_aoffi);
|
||||
bool is_array, bool is_aoffi,
|
||||
std::optional<Tegra::Shader::Register> bindless_reg);
|
||||
|
||||
Node4 GetTexsCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
|
||||
Tegra::Shader::TextureProcessMode process_mode, bool depth_compare,
|
||||
@@ -763,7 +792,8 @@ private:
|
||||
|
||||
Node4 GetTextureCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type,
|
||||
Tegra::Shader::TextureProcessMode process_mode, std::vector<Node> coords,
|
||||
Node array, Node depth_compare, u32 bias_offset, std::vector<Node> aoffi);
|
||||
Node array, Node depth_compare, u32 bias_offset, std::vector<Node> aoffi,
|
||||
std::optional<Tegra::Shader::Register> bindless_reg);
|
||||
|
||||
Node GetVideoOperand(Node op, bool is_chunk, bool is_signed, Tegra::Shader::VideoType type,
|
||||
u64 byte_height);
|
||||
|
||||
@@ -294,8 +294,6 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
|
||||
return PixelFormat::Z16;
|
||||
case Tegra::Texture::TextureFormat::Z24S8:
|
||||
return PixelFormat::Z24S8;
|
||||
case Tegra::Texture::TextureFormat::ZF32_X24S8:
|
||||
return PixelFormat::Z32FS8;
|
||||
case Tegra::Texture::TextureFormat::DXT1:
|
||||
return is_srgb ? PixelFormat::DXT1_SRGB : PixelFormat::DXT1;
|
||||
case Tegra::Texture::TextureFormat::DXT23:
|
||||
|
||||
@@ -1,386 +0,0 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/cityhash.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/texture_cache.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
|
||||
namespace VideoCommon {
|
||||
|
||||
using VideoCore::Surface::SurfaceTarget;
|
||||
|
||||
using VideoCore::Surface::ComponentTypeFromDepthFormat;
|
||||
using VideoCore::Surface::ComponentTypeFromRenderTarget;
|
||||
using VideoCore::Surface::ComponentTypeFromTexture;
|
||||
using VideoCore::Surface::PixelFormatFromDepthFormat;
|
||||
using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
|
||||
using VideoCore::Surface::PixelFormatFromTextureFormat;
|
||||
using VideoCore::Surface::SurfaceTargetFromTextureType;
|
||||
|
||||
constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) {
|
||||
return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile);
|
||||
}
|
||||
|
||||
SurfaceParams SurfaceParams::CreateForTexture(Core::System& system,
|
||||
const Tegra::Texture::FullTextureInfo& config) {
|
||||
SurfaceParams params;
|
||||
params.is_tiled = config.tic.IsTiled();
|
||||
params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0,
|
||||
params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0,
|
||||
params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0,
|
||||
params.tile_width_spacing = params.is_tiled ? (1 << config.tic.tile_width_spacing.Value()) : 1;
|
||||
params.pixel_format =
|
||||
PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(), false);
|
||||
params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value());
|
||||
params.type = GetFormatType(params.pixel_format);
|
||||
params.target = SurfaceTargetFromTextureType(config.tic.texture_type);
|
||||
params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format));
|
||||
params.height = Common::AlignUp(config.tic.Height(), GetCompressionFactor(params.pixel_format));
|
||||
params.depth = config.tic.Depth();
|
||||
if (params.target == SurfaceTarget::TextureCubemap ||
|
||||
params.target == SurfaceTarget::TextureCubeArray) {
|
||||
params.depth *= 6;
|
||||
}
|
||||
params.pitch = params.is_tiled ? 0 : config.tic.Pitch();
|
||||
params.unaligned_height = config.tic.Height();
|
||||
params.num_levels = config.tic.max_mip_level + 1;
|
||||
|
||||
params.CalculateCachedValues();
|
||||
return params;
|
||||
}
|
||||
|
||||
SurfaceParams SurfaceParams::CreateForDepthBuffer(
|
||||
Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format,
|
||||
u32 block_width, u32 block_height, u32 block_depth,
|
||||
Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) {
|
||||
SurfaceParams params;
|
||||
params.is_tiled = type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
|
||||
params.block_width = 1 << std::min(block_width, 5U);
|
||||
params.block_height = 1 << std::min(block_height, 5U);
|
||||
params.block_depth = 1 << std::min(block_depth, 5U);
|
||||
params.tile_width_spacing = 1;
|
||||
params.pixel_format = PixelFormatFromDepthFormat(format);
|
||||
params.component_type = ComponentTypeFromDepthFormat(format);
|
||||
params.type = GetFormatType(params.pixel_format);
|
||||
params.width = zeta_width;
|
||||
params.height = zeta_height;
|
||||
params.unaligned_height = zeta_height;
|
||||
params.target = SurfaceTarget::Texture2D;
|
||||
params.depth = 1;
|
||||
params.num_levels = 1;
|
||||
|
||||
params.CalculateCachedValues();
|
||||
return params;
|
||||
}
|
||||
|
||||
SurfaceParams SurfaceParams::CreateForFramebuffer(Core::System& system, std::size_t index) {
|
||||
const auto& config{system.GPU().Maxwell3D().regs.rt[index]};
|
||||
SurfaceParams params;
|
||||
params.is_tiled =
|
||||
config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
|
||||
params.block_width = 1 << config.memory_layout.block_width;
|
||||
params.block_height = 1 << config.memory_layout.block_height;
|
||||
params.block_depth = 1 << config.memory_layout.block_depth;
|
||||
params.tile_width_spacing = 1;
|
||||
params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
|
||||
params.component_type = ComponentTypeFromRenderTarget(config.format);
|
||||
params.type = GetFormatType(params.pixel_format);
|
||||
if (params.is_tiled) {
|
||||
params.width = config.width;
|
||||
} else {
|
||||
const u32 bpp = GetFormatBpp(params.pixel_format) / CHAR_BIT;
|
||||
params.pitch = config.width;
|
||||
params.width = params.pitch / bpp;
|
||||
}
|
||||
params.height = config.height;
|
||||
params.depth = 1;
|
||||
params.unaligned_height = config.height;
|
||||
params.target = SurfaceTarget::Texture2D;
|
||||
params.num_levels = 1;
|
||||
|
||||
params.CalculateCachedValues();
|
||||
return params;
|
||||
}
|
||||
|
||||
SurfaceParams SurfaceParams::CreateForFermiCopySurface(
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& config) {
|
||||
SurfaceParams params{};
|
||||
params.is_tiled = !config.linear;
|
||||
params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 32U) : 0,
|
||||
params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 32U) : 0,
|
||||
params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 32U) : 0,
|
||||
params.tile_width_spacing = 1;
|
||||
params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
|
||||
params.component_type = ComponentTypeFromRenderTarget(config.format);
|
||||
params.type = GetFormatType(params.pixel_format);
|
||||
params.width = config.width;
|
||||
params.height = config.height;
|
||||
params.unaligned_height = config.height;
|
||||
// TODO(Rodrigo): Try to guess the surface target from depth and layer parameters
|
||||
params.target = SurfaceTarget::Texture2D;
|
||||
params.depth = 1;
|
||||
params.num_levels = 1;
|
||||
|
||||
params.CalculateCachedValues();
|
||||
return params;
|
||||
}
|
||||
|
||||
u32 SurfaceParams::GetMipWidth(u32 level) const {
|
||||
return std::max(1U, width >> level);
|
||||
}
|
||||
|
||||
u32 SurfaceParams::GetMipHeight(u32 level) const {
|
||||
return std::max(1U, height >> level);
|
||||
}
|
||||
|
||||
u32 SurfaceParams::GetMipDepth(u32 level) const {
|
||||
return IsLayered() ? depth : std::max(1U, depth >> level);
|
||||
}
|
||||
|
||||
bool SurfaceParams::IsLayered() const {
|
||||
switch (target) {
|
||||
case SurfaceTarget::Texture1DArray:
|
||||
case SurfaceTarget::Texture2DArray:
|
||||
case SurfaceTarget::TextureCubeArray:
|
||||
case SurfaceTarget::TextureCubemap:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
u32 SurfaceParams::GetMipBlockHeight(u32 level) const {
|
||||
// Auto block resizing algorithm from:
|
||||
// https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nv50/nv50_miptree.c
|
||||
if (level == 0) {
|
||||
return block_height;
|
||||
}
|
||||
const u32 height{GetMipHeight(level)};
|
||||
const u32 default_block_height{GetDefaultBlockHeight(pixel_format)};
|
||||
const u32 blocks_in_y{(height + default_block_height - 1) / default_block_height};
|
||||
u32 block_height = 16;
|
||||
while (block_height > 1 && blocks_in_y <= block_height * 4) {
|
||||
block_height >>= 1;
|
||||
}
|
||||
return block_height;
|
||||
}
|
||||
|
||||
u32 SurfaceParams::GetMipBlockDepth(u32 level) const {
|
||||
if (level == 0)
|
||||
return block_depth;
|
||||
if (target != SurfaceTarget::Texture3D)
|
||||
return 1;
|
||||
|
||||
const u32 depth{GetMipDepth(level)};
|
||||
u32 block_depth = 32;
|
||||
while (block_depth > 1 && depth * 2 <= block_depth) {
|
||||
block_depth >>= 1;
|
||||
}
|
||||
if (block_depth == 32 && GetMipBlockHeight(level) >= 4) {
|
||||
return 16;
|
||||
}
|
||||
return block_depth;
|
||||
}
|
||||
|
||||
std::size_t SurfaceParams::GetGuestMipmapLevelOffset(u32 level) const {
|
||||
std::size_t offset = 0;
|
||||
for (u32 i = 0; i < level; i++) {
|
||||
offset += GetInnerMipmapMemorySize(i, false, IsLayered(), false);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
std::size_t SurfaceParams::GetHostMipmapLevelOffset(u32 level) const {
|
||||
std::size_t offset = 0;
|
||||
for (u32 i = 0; i < level; i++) {
|
||||
offset += GetInnerMipmapMemorySize(i, true, false, false);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
std::size_t SurfaceParams::GetGuestLayerSize() const {
|
||||
return GetInnerMemorySize(false, true, false);
|
||||
}
|
||||
|
||||
std::size_t SurfaceParams::GetHostLayerSize(u32 level) const {
|
||||
return GetInnerMipmapMemorySize(level, true, IsLayered(), false);
|
||||
}
|
||||
|
||||
bool SurfaceParams::IsFamiliar(const SurfaceParams& view_params) const {
|
||||
if (std::tie(is_tiled, tile_width_spacing, pixel_format, component_type, type) !=
|
||||
std::tie(view_params.is_tiled, view_params.tile_width_spacing, view_params.pixel_format,
|
||||
view_params.component_type, view_params.type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const SurfaceTarget view_target{view_params.target};
|
||||
if (view_target == target) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (target) {
|
||||
case SurfaceTarget::Texture1D:
|
||||
case SurfaceTarget::Texture2D:
|
||||
case SurfaceTarget::Texture3D:
|
||||
return false;
|
||||
case SurfaceTarget::Texture1DArray:
|
||||
return view_target == SurfaceTarget::Texture1D;
|
||||
case SurfaceTarget::Texture2DArray:
|
||||
return view_target == SurfaceTarget::Texture2D;
|
||||
case SurfaceTarget::TextureCubemap:
|
||||
return view_target == SurfaceTarget::Texture2D ||
|
||||
view_target == SurfaceTarget::Texture2DArray;
|
||||
case SurfaceTarget::TextureCubeArray:
|
||||
return view_target == SurfaceTarget::Texture2D ||
|
||||
view_target == SurfaceTarget::Texture2DArray ||
|
||||
view_target == SurfaceTarget::TextureCubemap;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented texture family={}", static_cast<u32>(target));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool SurfaceParams::IsPixelFormatZeta() const {
|
||||
return pixel_format >= VideoCore::Surface::PixelFormat::MaxColorFormat &&
|
||||
pixel_format < VideoCore::Surface::PixelFormat::MaxDepthStencilFormat;
|
||||
}
|
||||
|
||||
void SurfaceParams::CalculateCachedValues() {
|
||||
guest_size_in_bytes = GetInnerMemorySize(false, false, false);
|
||||
|
||||
// ASTC is uncompressed in software, in emulated as RGBA8
|
||||
if (IsPixelFormatASTC(pixel_format)) {
|
||||
host_size_in_bytes = width * height * depth * 4;
|
||||
} else {
|
||||
host_size_in_bytes = GetInnerMemorySize(true, false, false);
|
||||
}
|
||||
|
||||
switch (target) {
|
||||
case SurfaceTarget::Texture1D:
|
||||
case SurfaceTarget::Texture2D:
|
||||
case SurfaceTarget::Texture3D:
|
||||
num_layers = 1;
|
||||
break;
|
||||
case SurfaceTarget::Texture1DArray:
|
||||
case SurfaceTarget::Texture2DArray:
|
||||
case SurfaceTarget::TextureCubemap:
|
||||
case SurfaceTarget::TextureCubeArray:
|
||||
num_layers = depth;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t SurfaceParams::GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool layer_only,
|
||||
bool uncompressed) const {
|
||||
const bool tiled{as_host_size ? false : is_tiled};
|
||||
const u32 tile_x{GetDefaultBlockWidth(pixel_format)};
|
||||
const u32 tile_y{GetDefaultBlockHeight(pixel_format)};
|
||||
const u32 width{GetMipmapSize(uncompressed, GetMipWidth(level), tile_x)};
|
||||
const u32 height{GetMipmapSize(uncompressed, GetMipHeight(level), tile_y)};
|
||||
const u32 depth{layer_only ? 1U : GetMipDepth(level)};
|
||||
return Tegra::Texture::CalculateSize(tiled, GetBytesPerPixel(pixel_format), width, height,
|
||||
depth, GetMipBlockHeight(level), GetMipBlockDepth(level));
|
||||
}
|
||||
|
||||
std::size_t SurfaceParams::GetInnerMemorySize(bool as_host_size, bool layer_only,
|
||||
bool uncompressed) const {
|
||||
std::size_t size = 0;
|
||||
for (u32 level = 0; level < num_levels; ++level) {
|
||||
size += GetInnerMipmapMemorySize(level, as_host_size, layer_only, uncompressed);
|
||||
}
|
||||
if (!as_host_size && is_tiled) {
|
||||
size = Common::AlignUp(size, Tegra::Texture::GetGOBSize() * block_height * block_depth);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
std::map<u64, std::pair<u32, u32>> SurfaceParams::CreateViewOffsetMap() const {
|
||||
std::map<u64, std::pair<u32, u32>> view_offset_map;
|
||||
switch (target) {
|
||||
case SurfaceTarget::Texture1D:
|
||||
case SurfaceTarget::Texture2D:
|
||||
case SurfaceTarget::Texture3D: {
|
||||
constexpr u32 layer = 0;
|
||||
for (u32 level = 0; level < num_levels; ++level) {
|
||||
const std::size_t offset{GetGuestMipmapLevelOffset(level)};
|
||||
view_offset_map.insert({offset, {layer, level}});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SurfaceTarget::Texture1DArray:
|
||||
case SurfaceTarget::Texture2DArray:
|
||||
case SurfaceTarget::TextureCubemap:
|
||||
case SurfaceTarget::TextureCubeArray: {
|
||||
const std::size_t layer_size{GetGuestLayerSize()};
|
||||
for (u32 level = 0; level < num_levels; ++level) {
|
||||
const std::size_t level_offset{GetGuestMipmapLevelOffset(level)};
|
||||
for (u32 layer = 0; layer < num_layers; ++layer) {
|
||||
const auto layer_offset{static_cast<std::size_t>(layer_size * layer)};
|
||||
const std::size_t offset{level_offset + layer_offset};
|
||||
view_offset_map.insert({offset, {layer, level}});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented surface target {}", static_cast<u32>(target));
|
||||
}
|
||||
return view_offset_map;
|
||||
}
|
||||
|
||||
bool SurfaceParams::IsViewValid(const SurfaceParams& view_params, u32 layer, u32 level) const {
|
||||
return IsDimensionValid(view_params, level) && IsDepthValid(view_params, level) &&
|
||||
IsInBounds(view_params, layer, level);
|
||||
}
|
||||
|
||||
bool SurfaceParams::IsDimensionValid(const SurfaceParams& view_params, u32 level) const {
|
||||
return view_params.width == GetMipWidth(level) && view_params.height == GetMipHeight(level);
|
||||
}
|
||||
|
||||
bool SurfaceParams::IsDepthValid(const SurfaceParams& view_params, u32 level) const {
|
||||
if (view_params.target != SurfaceTarget::Texture3D) {
|
||||
return true;
|
||||
}
|
||||
return view_params.depth == GetMipDepth(level);
|
||||
}
|
||||
|
||||
bool SurfaceParams::IsInBounds(const SurfaceParams& view_params, u32 layer, u32 level) const {
|
||||
return layer + view_params.num_layers <= num_layers &&
|
||||
level + view_params.num_levels <= num_levels;
|
||||
}
|
||||
|
||||
std::size_t HasheableSurfaceParams::Hash() const {
|
||||
return static_cast<std::size_t>(
|
||||
Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this)));
|
||||
}
|
||||
|
||||
bool HasheableSurfaceParams::operator==(const HasheableSurfaceParams& rhs) const {
|
||||
return std::tie(is_tiled, block_width, block_height, block_depth, tile_width_spacing, width,
|
||||
height, depth, pitch, unaligned_height, num_levels, pixel_format,
|
||||
component_type, type, target) ==
|
||||
std::tie(rhs.is_tiled, rhs.block_width, rhs.block_height, rhs.block_depth,
|
||||
rhs.tile_width_spacing, rhs.width, rhs.height, rhs.depth, rhs.pitch,
|
||||
rhs.unaligned_height, rhs.num_levels, rhs.pixel_format, rhs.component_type,
|
||||
rhs.type, rhs.target);
|
||||
}
|
||||
|
||||
std::size_t ViewKey::Hash() const {
|
||||
return static_cast<std::size_t>(
|
||||
Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this)));
|
||||
}
|
||||
|
||||
bool ViewKey::operator==(const ViewKey& rhs) const {
|
||||
return std::tie(base_layer, num_layers, base_level, num_levels) ==
|
||||
std::tie(rhs.base_layer, rhs.num_layers, rhs.base_level, rhs.num_levels);
|
||||
}
|
||||
|
||||
} // namespace VideoCommon
|
||||
@@ -1,586 +0,0 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <boost/icl/interval_map.hpp>
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/surface.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Tegra::Texture {
|
||||
struct FullTextureInfo;
|
||||
}
|
||||
|
||||
namespace VideoCore {
|
||||
class RasterizerInterface;
|
||||
}
|
||||
|
||||
namespace VideoCommon {
|
||||
|
||||
class HasheableSurfaceParams {
|
||||
public:
|
||||
std::size_t Hash() const;
|
||||
|
||||
bool operator==(const HasheableSurfaceParams& rhs) const;
|
||||
|
||||
protected:
|
||||
// Avoid creation outside of a managed environment.
|
||||
HasheableSurfaceParams() = default;
|
||||
|
||||
bool is_tiled;
|
||||
u32 block_width;
|
||||
u32 block_height;
|
||||
u32 block_depth;
|
||||
u32 tile_width_spacing;
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 depth;
|
||||
u32 pitch;
|
||||
u32 unaligned_height;
|
||||
u32 num_levels;
|
||||
VideoCore::Surface::PixelFormat pixel_format;
|
||||
VideoCore::Surface::ComponentType component_type;
|
||||
VideoCore::Surface::SurfaceType type;
|
||||
VideoCore::Surface::SurfaceTarget target;
|
||||
};
|
||||
|
||||
class SurfaceParams final : public HasheableSurfaceParams {
|
||||
public:
|
||||
/// Creates SurfaceCachedParams from a texture configuration.
|
||||
static SurfaceParams CreateForTexture(Core::System& system,
|
||||
const Tegra::Texture::FullTextureInfo& config);
|
||||
|
||||
/// Creates SurfaceCachedParams for a depth buffer configuration.
|
||||
static SurfaceParams CreateForDepthBuffer(
|
||||
Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format,
|
||||
u32 block_width, u32 block_height, u32 block_depth,
|
||||
Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type);
|
||||
|
||||
/// Creates SurfaceCachedParams from a framebuffer configuration.
|
||||
static SurfaceParams CreateForFramebuffer(Core::System& system, std::size_t index);
|
||||
|
||||
/// Creates SurfaceCachedParams from a Fermi2D surface configuration.
|
||||
static SurfaceParams CreateForFermiCopySurface(
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& config);
|
||||
|
||||
bool IsTiled() const {
|
||||
return is_tiled;
|
||||
}
|
||||
|
||||
u32 GetBlockWidth() const {
|
||||
return block_width;
|
||||
}
|
||||
|
||||
u32 GetTileWidthSpacing() const {
|
||||
return tile_width_spacing;
|
||||
}
|
||||
|
||||
u32 GetWidth() const {
|
||||
return width;
|
||||
}
|
||||
|
||||
u32 GetHeight() const {
|
||||
return height;
|
||||
}
|
||||
|
||||
u32 GetDepth() const {
|
||||
return depth;
|
||||
}
|
||||
|
||||
u32 GetPitch() const {
|
||||
return pitch;
|
||||
}
|
||||
|
||||
u32 GetNumLevels() const {
|
||||
return num_levels;
|
||||
}
|
||||
|
||||
VideoCore::Surface::PixelFormat GetPixelFormat() const {
|
||||
return pixel_format;
|
||||
}
|
||||
|
||||
VideoCore::Surface::ComponentType GetComponentType() const {
|
||||
return component_type;
|
||||
}
|
||||
|
||||
VideoCore::Surface::SurfaceTarget GetTarget() const {
|
||||
return target;
|
||||
}
|
||||
|
||||
VideoCore::Surface::SurfaceType GetType() const {
|
||||
return type;
|
||||
}
|
||||
|
||||
std::size_t GetGuestSizeInBytes() const {
|
||||
return guest_size_in_bytes;
|
||||
}
|
||||
|
||||
std::size_t GetHostSizeInBytes() const {
|
||||
return host_size_in_bytes;
|
||||
}
|
||||
|
||||
u32 GetNumLayers() const {
|
||||
return num_layers;
|
||||
}
|
||||
|
||||
/// Returns the width of a given mipmap level.
|
||||
u32 GetMipWidth(u32 level) const;
|
||||
|
||||
/// Returns the height of a given mipmap level.
|
||||
u32 GetMipHeight(u32 level) const;
|
||||
|
||||
/// Returns the depth of a given mipmap level.
|
||||
u32 GetMipDepth(u32 level) const;
|
||||
|
||||
/// Returns true if these parameters are from a layered surface.
|
||||
bool IsLayered() const;
|
||||
|
||||
/// Returns the block height of a given mipmap level.
|
||||
u32 GetMipBlockHeight(u32 level) const;
|
||||
|
||||
/// Returns the block depth of a given mipmap level.
|
||||
u32 GetMipBlockDepth(u32 level) const;
|
||||
|
||||
/// Returns the offset in bytes in guest memory of a given mipmap level.
|
||||
std::size_t GetGuestMipmapLevelOffset(u32 level) const;
|
||||
|
||||
/// Returns the offset in bytes in host memory (linear) of a given mipmap level.
|
||||
std::size_t GetHostMipmapLevelOffset(u32 level) const;
|
||||
|
||||
/// Returns the size of a layer in bytes in guest memory.
|
||||
std::size_t GetGuestLayerSize() const;
|
||||
|
||||
/// Returns the size of a layer in bytes in host memory for a given mipmap level.
|
||||
std::size_t GetHostLayerSize(u32 level) const;
|
||||
|
||||
/// Returns true if another surface can be familiar with this. This is a loosely defined term
|
||||
/// that reflects the possibility of these two surface parameters potentially being part of a
|
||||
/// bigger superset.
|
||||
bool IsFamiliar(const SurfaceParams& view_params) const;
|
||||
|
||||
/// Returns true if the pixel format is a depth and/or stencil format.
|
||||
bool IsPixelFormatZeta() const;
|
||||
|
||||
/// Creates a map that redirects an address difference to a layer and mipmap level.
|
||||
std::map<u64, std::pair<u32, u32>> CreateViewOffsetMap() const;
|
||||
|
||||
/// Returns true if the passed surface view parameters is equal or a valid subset of this.
|
||||
bool IsViewValid(const SurfaceParams& view_params, u32 layer, u32 level) const;
|
||||
|
||||
private:
|
||||
/// Calculates values that can be deduced from HasheableSurfaceParams.
|
||||
void CalculateCachedValues();
|
||||
|
||||
/// Returns the size of a given mipmap level.
|
||||
std::size_t GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool layer_only,
|
||||
bool uncompressed) const;
|
||||
|
||||
/// Returns the size of all mipmap levels and aligns as needed.
|
||||
std::size_t GetInnerMemorySize(bool as_host_size, bool layer_only, bool uncompressed) const;
|
||||
|
||||
/// Returns true if the passed view width and height match the size of this params in a given
|
||||
/// mipmap level.
|
||||
bool IsDimensionValid(const SurfaceParams& view_params, u32 level) const;
|
||||
|
||||
/// Returns true if the passed view depth match the size of this params in a given mipmap level.
|
||||
bool IsDepthValid(const SurfaceParams& view_params, u32 level) const;
|
||||
|
||||
/// Returns true if the passed view layers and mipmap levels are in bounds.
|
||||
bool IsInBounds(const SurfaceParams& view_params, u32 layer, u32 level) const;
|
||||
|
||||
std::size_t guest_size_in_bytes;
|
||||
std::size_t host_size_in_bytes;
|
||||
u32 num_layers;
|
||||
};
|
||||
|
||||
struct ViewKey {
|
||||
std::size_t Hash() const;
|
||||
|
||||
bool operator==(const ViewKey& rhs) const;
|
||||
|
||||
u32 base_layer{};
|
||||
u32 num_layers{};
|
||||
u32 base_level{};
|
||||
u32 num_levels{};
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<VideoCommon::SurfaceParams> {
|
||||
std::size_t operator()(const VideoCommon::SurfaceParams& k) const noexcept {
|
||||
return k.Hash();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<VideoCommon::ViewKey> {
|
||||
std::size_t operator()(const VideoCommon::ViewKey& k) const noexcept {
|
||||
return k.Hash();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
namespace VideoCommon {
|
||||
|
||||
template <typename TView, typename TExecutionContext>
|
||||
class SurfaceBase {
|
||||
static_assert(std::is_trivially_copyable_v<TExecutionContext>);
|
||||
|
||||
public:
|
||||
virtual void LoadBuffer() = 0;
|
||||
|
||||
virtual TExecutionContext FlushBuffer(TExecutionContext exctx) = 0;
|
||||
|
||||
virtual TExecutionContext UploadTexture(TExecutionContext exctx) = 0;
|
||||
|
||||
TView* TryGetView(VAddr view_addr, const SurfaceParams& view_params) {
|
||||
if (view_addr < cpu_addr || !params.IsFamiliar(view_params)) {
|
||||
// It can't be a view if it's in a prior address.
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto relative_offset{static_cast<u64>(view_addr - cpu_addr)};
|
||||
const auto it{view_offset_map.find(relative_offset)};
|
||||
if (it == view_offset_map.end()) {
|
||||
// Couldn't find an aligned view.
|
||||
return {};
|
||||
}
|
||||
const auto [layer, level] = it->second;
|
||||
|
||||
if (!params.IsViewValid(view_params, layer, level)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return GetView(layer, view_params.GetNumLayers(), level, view_params.GetNumLevels());
|
||||
}
|
||||
|
||||
VAddr GetCpuAddr() const {
|
||||
ASSERT(is_registered);
|
||||
return cpu_addr;
|
||||
}
|
||||
|
||||
u8* GetHostPtr() const {
|
||||
ASSERT(is_registered);
|
||||
return host_ptr;
|
||||
}
|
||||
|
||||
CacheAddr GetCacheAddr() const {
|
||||
ASSERT(is_registered);
|
||||
return cache_addr;
|
||||
}
|
||||
|
||||
std::size_t GetSizeInBytes() const {
|
||||
return params.GetGuestSizeInBytes();
|
||||
}
|
||||
|
||||
void MarkAsModified(bool is_modified_) {
|
||||
is_modified = is_modified_;
|
||||
}
|
||||
|
||||
const SurfaceParams& GetSurfaceParams() const {
|
||||
return params;
|
||||
}
|
||||
|
||||
TView* GetView(VAddr view_addr, const SurfaceParams& view_params) {
|
||||
TView* view{TryGetView(view_addr, view_params)};
|
||||
ASSERT(view != nullptr);
|
||||
return view;
|
||||
}
|
||||
|
||||
void Register(VAddr cpu_addr_, u8* host_ptr_) {
|
||||
ASSERT(!is_registered);
|
||||
is_registered = true;
|
||||
cpu_addr = cpu_addr_;
|
||||
host_ptr = host_ptr_;
|
||||
cache_addr = ToCacheAddr(host_ptr_);
|
||||
}
|
||||
|
||||
void Register(VAddr cpu_addr_) {
|
||||
Register(cpu_addr_, Memory::GetPointer(cpu_addr_));
|
||||
}
|
||||
|
||||
void Unregister() {
|
||||
ASSERT(is_registered);
|
||||
is_registered = false;
|
||||
}
|
||||
|
||||
bool IsRegistered() const {
|
||||
return is_registered;
|
||||
}
|
||||
|
||||
protected:
|
||||
explicit SurfaceBase(const SurfaceParams& params)
|
||||
: params{params}, view_offset_map{params.CreateViewOffsetMap()} {}
|
||||
|
||||
~SurfaceBase() = default;
|
||||
|
||||
virtual std::unique_ptr<TView> CreateView(const ViewKey& view_key) = 0;
|
||||
|
||||
bool IsModified() const {
|
||||
return is_modified;
|
||||
}
|
||||
|
||||
const SurfaceParams params;
|
||||
|
||||
private:
|
||||
TView* GetView(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels) {
|
||||
const ViewKey key{base_layer, num_layers, base_level, num_levels};
|
||||
const auto [entry, is_cache_miss] = views.try_emplace(key);
|
||||
auto& view{entry->second};
|
||||
if (is_cache_miss) {
|
||||
view = CreateView(key);
|
||||
}
|
||||
return view.get();
|
||||
}
|
||||
|
||||
const std::map<u64, std::pair<u32, u32>> view_offset_map;
|
||||
|
||||
VAddr cpu_addr{};
|
||||
u8* host_ptr{};
|
||||
CacheAddr cache_addr{};
|
||||
bool is_modified{};
|
||||
bool is_registered{};
|
||||
std::unordered_map<ViewKey, std::unique_ptr<TView>> views;
|
||||
};
|
||||
|
||||
template <typename TSurface, typename TView, typename TExecutionContext>
|
||||
class TextureCache {
|
||||
static_assert(std::is_trivially_copyable_v<TExecutionContext>);
|
||||
using ResultType = std::tuple<TView*, TExecutionContext>;
|
||||
using IntervalMap = boost::icl::interval_map<CacheAddr, std::set<TSurface*>>;
|
||||
using IntervalType = typename IntervalMap::interval_type;
|
||||
|
||||
public:
|
||||
void InvalidateRegion(CacheAddr addr, std::size_t size) {
|
||||
for (TSurface* surface : GetSurfacesInRegion(addr, size)) {
|
||||
if (!surface->IsRegistered()) {
|
||||
// Skip duplicates
|
||||
continue;
|
||||
}
|
||||
Unregister(surface);
|
||||
}
|
||||
}
|
||||
|
||||
ResultType GetTextureSurface(TExecutionContext exctx,
|
||||
const Tegra::Texture::FullTextureInfo& config) {
|
||||
auto& memory_manager{system.GPU().MemoryManager()};
|
||||
const auto cpu_addr{memory_manager.GpuToCpuAddress(config.tic.Address())};
|
||||
if (!cpu_addr) {
|
||||
return {{}, exctx};
|
||||
}
|
||||
const auto params{SurfaceParams::CreateForTexture(system, config)};
|
||||
return GetSurfaceView(exctx, *cpu_addr, params, true);
|
||||
}
|
||||
|
||||
ResultType GetDepthBufferSurface(TExecutionContext exctx, bool preserve_contents) {
|
||||
const auto& regs{system.GPU().Maxwell3D().regs};
|
||||
if (!regs.zeta.Address() || !regs.zeta_enable) {
|
||||
return {{}, exctx};
|
||||
}
|
||||
|
||||
auto& memory_manager{system.GPU().MemoryManager()};
|
||||
const auto cpu_addr{memory_manager.GpuToCpuAddress(regs.zeta.Address())};
|
||||
if (!cpu_addr) {
|
||||
return {{}, exctx};
|
||||
}
|
||||
|
||||
const auto depth_params{SurfaceParams::CreateForDepthBuffer(
|
||||
system, regs.zeta_width, regs.zeta_height, regs.zeta.format,
|
||||
regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height,
|
||||
regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)};
|
||||
return GetSurfaceView(exctx, *cpu_addr, depth_params, preserve_contents);
|
||||
}
|
||||
|
||||
ResultType GetColorBufferSurface(TExecutionContext exctx, std::size_t index,
|
||||
bool preserve_contents) {
|
||||
ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets);
|
||||
|
||||
const auto& regs{system.GPU().Maxwell3D().regs};
|
||||
if (index >= regs.rt_control.count || regs.rt[index].Address() == 0 ||
|
||||
regs.rt[index].format == Tegra::RenderTargetFormat::NONE) {
|
||||
return {{}, exctx};
|
||||
}
|
||||
|
||||
auto& memory_manager{system.GPU().MemoryManager()};
|
||||
const auto& config{system.GPU().Maxwell3D().regs.rt[index]};
|
||||
const auto cpu_addr{memory_manager.GpuToCpuAddress(
|
||||
config.Address() + config.base_layer * config.layer_stride * sizeof(u32))};
|
||||
if (!cpu_addr) {
|
||||
return {{}, exctx};
|
||||
}
|
||||
|
||||
return GetSurfaceView(exctx, *cpu_addr, SurfaceParams::CreateForFramebuffer(system, index),
|
||||
preserve_contents);
|
||||
}
|
||||
|
||||
ResultType GetFermiSurface(TExecutionContext exctx,
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& config) {
|
||||
const auto cpu_addr{system.GPU().MemoryManager().GpuToCpuAddress(config.Address())};
|
||||
ASSERT(cpu_addr);
|
||||
return GetSurfaceView(exctx, *cpu_addr, SurfaceParams::CreateForFermiCopySurface(config),
|
||||
true);
|
||||
}
|
||||
|
||||
TSurface* TryFindFramebufferSurface(const u8* host_ptr) const {
|
||||
const auto it{registered_surfaces.find(ToCacheAddr(host_ptr))};
|
||||
return it != registered_surfaces.end() ? *it->second.begin() : nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
TextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer)
|
||||
: system{system}, rasterizer{rasterizer} {}
|
||||
|
||||
~TextureCache() = default;
|
||||
|
||||
virtual ResultType TryFastGetSurfaceView(TExecutionContext exctx, VAddr cpu_addr, u8* host_ptr,
|
||||
const SurfaceParams& params, bool preserve_contents,
|
||||
const std::vector<TSurface*>& overlaps) = 0;
|
||||
|
||||
virtual std::unique_ptr<TSurface> CreateSurface(const SurfaceParams& params) = 0;
|
||||
|
||||
void Register(TSurface* surface, VAddr cpu_addr, u8* host_ptr) {
|
||||
surface->Register(cpu_addr, host_ptr);
|
||||
registered_surfaces.add({GetSurfaceInterval(surface), {surface}});
|
||||
rasterizer.UpdatePagesCachedCount(surface->GetCpuAddr(), surface->GetSizeInBytes(), 1);
|
||||
}
|
||||
|
||||
void Unregister(TSurface* surface) {
|
||||
registered_surfaces.subtract({GetSurfaceInterval(surface), {surface}});
|
||||
rasterizer.UpdatePagesCachedCount(surface->GetCpuAddr(), surface->GetSizeInBytes(), -1);
|
||||
surface->Unregister();
|
||||
}
|
||||
|
||||
TSurface* GetUncachedSurface(const SurfaceParams& params) {
|
||||
if (TSurface* surface = TryGetReservedSurface(params); surface)
|
||||
return surface;
|
||||
// No reserved surface available, create a new one and reserve it
|
||||
auto new_surface{CreateSurface(params)};
|
||||
TSurface* surface{new_surface.get()};
|
||||
ReserveSurface(params, std::move(new_surface));
|
||||
return surface;
|
||||
}
|
||||
|
||||
Core::System& system;
|
||||
|
||||
private:
|
||||
ResultType GetSurfaceView(TExecutionContext exctx, VAddr cpu_addr, const SurfaceParams& params,
|
||||
bool preserve_contents) {
|
||||
const auto host_ptr{Memory::GetPointer(cpu_addr)};
|
||||
const auto cache_addr{ToCacheAddr(host_ptr)};
|
||||
const auto overlaps{GetSurfacesInRegion(cache_addr, params.GetGuestSizeInBytes())};
|
||||
if (overlaps.empty()) {
|
||||
return LoadSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents);
|
||||
}
|
||||
|
||||
if (overlaps.size() == 1) {
|
||||
if (TView* view = overlaps[0]->TryGetView(cpu_addr, params); view)
|
||||
return {view, exctx};
|
||||
}
|
||||
|
||||
TView* fast_view;
|
||||
std::tie(fast_view, exctx) =
|
||||
TryFastGetSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents, overlaps);
|
||||
|
||||
for (TSurface* surface : overlaps) {
|
||||
if (!fast_view) {
|
||||
// Flush even when we don't care about the contents, to preserve memory not written
|
||||
// by the new surface.
|
||||
exctx = surface->FlushBuffer(exctx);
|
||||
}
|
||||
Unregister(surface);
|
||||
}
|
||||
|
||||
if (fast_view) {
|
||||
return {fast_view, exctx};
|
||||
}
|
||||
|
||||
return LoadSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents);
|
||||
}
|
||||
|
||||
ResultType LoadSurfaceView(TExecutionContext exctx, VAddr cpu_addr, u8* host_ptr,
|
||||
const SurfaceParams& params, bool preserve_contents) {
|
||||
TSurface* new_surface{GetUncachedSurface(params)};
|
||||
Register(new_surface, cpu_addr, host_ptr);
|
||||
if (preserve_contents) {
|
||||
exctx = LoadSurface(exctx, new_surface);
|
||||
}
|
||||
return {new_surface->GetView(cpu_addr, params), exctx};
|
||||
}
|
||||
|
||||
TExecutionContext LoadSurface(TExecutionContext exctx, TSurface* surface) {
|
||||
surface->LoadBuffer();
|
||||
exctx = surface->UploadTexture(exctx);
|
||||
surface->MarkAsModified(false);
|
||||
return exctx;
|
||||
}
|
||||
|
||||
std::vector<TSurface*> GetSurfacesInRegion(CacheAddr cache_addr, std::size_t size) const {
|
||||
if (size == 0) {
|
||||
return {};
|
||||
}
|
||||
const IntervalType interval{cache_addr, cache_addr + size};
|
||||
|
||||
std::vector<TSurface*> surfaces;
|
||||
for (auto& pair : boost::make_iterator_range(registered_surfaces.equal_range(interval))) {
|
||||
surfaces.push_back(*pair.second.begin());
|
||||
}
|
||||
return surfaces;
|
||||
}
|
||||
|
||||
void ReserveSurface(const SurfaceParams& params, std::unique_ptr<TSurface> surface) {
|
||||
surface_reserve[params].push_back(std::move(surface));
|
||||
}
|
||||
|
||||
TSurface* TryGetReservedSurface(const SurfaceParams& params) {
|
||||
auto search{surface_reserve.find(params)};
|
||||
if (search == surface_reserve.end()) {
|
||||
return {};
|
||||
}
|
||||
for (auto& surface : search->second) {
|
||||
if (!surface->IsRegistered()) {
|
||||
return surface.get();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
IntervalType GetSurfaceInterval(TSurface* surface) const {
|
||||
return IntervalType::right_open(surface->GetCacheAddr(),
|
||||
surface->GetCacheAddr() + surface->GetSizeInBytes());
|
||||
}
|
||||
|
||||
VideoCore::RasterizerInterface& rasterizer;
|
||||
|
||||
IntervalMap registered_surfaces;
|
||||
|
||||
/// The surface reserve is a "backup" cache, this is where we put unique surfaces that have
|
||||
/// previously been used. This is to prevent surfaces from being constantly created and
|
||||
/// destroyed when used with different surface parameters.
|
||||
std::unordered_map<SurfaceParams, std::list<std::unique_ptr<TSurface>>> surface_reserve;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/textures/astc.h"
|
||||
#include "video_core/textures/convert.h"
|
||||
|
||||
|
||||
@@ -5,10 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace VideoCore::Surface {
|
||||
enum class PixelFormat;
|
||||
}
|
||||
#include "video_core/surface.h"
|
||||
|
||||
namespace Tegra::Texture {
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
#include <array>
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
|
||||
namespace Tegra::Texture {
|
||||
|
||||
|
||||
@@ -31,8 +31,6 @@ add_executable(yuzu
|
||||
configuration/configure_general.h
|
||||
configuration/configure_graphics.cpp
|
||||
configuration/configure_graphics.h
|
||||
configuration/configure_hotkeys.cpp
|
||||
configuration/configure_hotkeys.h
|
||||
configuration/configure_input.cpp
|
||||
configuration/configure_input.h
|
||||
configuration/configure_input_player.cpp
|
||||
@@ -56,6 +54,8 @@ add_executable(yuzu
|
||||
debugger/graphics/graphics_breakpoints.cpp
|
||||
debugger/graphics/graphics_breakpoints.h
|
||||
debugger/graphics/graphics_breakpoints_p.h
|
||||
debugger/graphics/graphics_surface.cpp
|
||||
debugger/graphics/graphics_surface.h
|
||||
debugger/console.cpp
|
||||
debugger/console.h
|
||||
debugger/profiler.cpp
|
||||
@@ -78,8 +78,6 @@ add_executable(yuzu
|
||||
ui_settings.h
|
||||
util/limitable_input_dialog.cpp
|
||||
util/limitable_input_dialog.h
|
||||
util/sequence_dialog/sequence_dialog.cpp
|
||||
util/sequence_dialog/sequence_dialog.h
|
||||
util/spinbox.cpp
|
||||
util/spinbox.h
|
||||
util/util.cpp
|
||||
@@ -97,7 +95,6 @@ set(UIS
|
||||
configuration/configure_gamelist.ui
|
||||
configuration/configure_general.ui
|
||||
configuration/configure_graphics.ui
|
||||
configuration/configure_hotkeys.ui
|
||||
configuration/configure_input.ui
|
||||
configuration/configure_input_player.ui
|
||||
configuration/configure_input_simple.ui
|
||||
@@ -108,6 +105,7 @@ set(UIS
|
||||
configuration/configure_touchscreen_advanced.ui
|
||||
configuration/configure_web.ui
|
||||
compatdb.ui
|
||||
hotkeys.ui
|
||||
loading_screen.ui
|
||||
main.ui
|
||||
)
|
||||
@@ -151,12 +149,6 @@ target_link_libraries(yuzu PRIVATE common core input_common video_core)
|
||||
target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets)
|
||||
target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
|
||||
|
||||
target_compile_definitions(yuzu PRIVATE
|
||||
# Use QStringBuilder for string concatenation to reduce
|
||||
# the overall number of temporary strings created.
|
||||
-DQT_USE_QSTRINGBUILDER
|
||||
)
|
||||
|
||||
if (YUZU_ENABLE_COMPATIBILITY_REPORTING)
|
||||
target_compile_definitions(yuzu PRIVATE -DYUZU_ENABLE_COMPATIBILITY_REPORTING)
|
||||
endif()
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include <mutex>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QScrollArea>
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <vector>
|
||||
#include <QDialog>
|
||||
#include <QList>
|
||||
#include <QTreeView>
|
||||
#include "core/frontend/applets/profile_select.h"
|
||||
|
||||
class GMainWindow;
|
||||
@@ -17,6 +16,7 @@ class QLabel;
|
||||
class QScrollArea;
|
||||
class QStandardItem;
|
||||
class QStandardItemModel;
|
||||
class QTreeView;
|
||||
class QVBoxLayout;
|
||||
|
||||
class QtProfileSelectionDialog final : public QDialog {
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QApplication>
|
||||
#include <QHBoxLayout>
|
||||
#include <QKeyEvent>
|
||||
#include <QOffscreenSurface>
|
||||
#include <QOpenGLWindow>
|
||||
#include <QPainter>
|
||||
#include <QScreen>
|
||||
#include <QWindow>
|
||||
#include <fmt/format.h>
|
||||
@@ -89,36 +82,13 @@ void EmuThread::run() {
|
||||
render_window->moveContext();
|
||||
}
|
||||
|
||||
class GGLContext : public Core::Frontend::GraphicsContext {
|
||||
public:
|
||||
explicit GGLContext(QOpenGLContext* shared_context) : surface() {
|
||||
context = std::make_unique<QOpenGLContext>(shared_context);
|
||||
surface.setFormat(shared_context->format());
|
||||
surface.create();
|
||||
}
|
||||
|
||||
void MakeCurrent() override {
|
||||
context->makeCurrent(&surface);
|
||||
}
|
||||
|
||||
void DoneCurrent() override {
|
||||
context->doneCurrent();
|
||||
}
|
||||
|
||||
void SwapBuffers() override {}
|
||||
|
||||
private:
|
||||
std::unique_ptr<QOpenGLContext> context;
|
||||
QOffscreenSurface surface;
|
||||
};
|
||||
|
||||
// This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL
|
||||
// context.
|
||||
// The corresponding functionality is handled in EmuThread instead
|
||||
class GGLWidgetInternal : public QOpenGLWindow {
|
||||
class GGLWidgetInternal : public QGLWidget {
|
||||
public:
|
||||
GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context)
|
||||
: QOpenGLWindow(shared_context), parent(parent) {}
|
||||
GGLWidgetInternal(QGLFormat fmt, GRenderWindow* parent)
|
||||
: QGLWidget(fmt, parent), parent(parent) {}
|
||||
|
||||
void paintEvent(QPaintEvent* ev) override {
|
||||
if (do_painting) {
|
||||
@@ -131,51 +101,9 @@ public:
|
||||
parent->OnFramebufferSizeChanged();
|
||||
}
|
||||
|
||||
void keyPressEvent(QKeyEvent* event) override {
|
||||
InputCommon::GetKeyboard()->PressKey(event->key());
|
||||
}
|
||||
|
||||
void keyReleaseEvent(QKeyEvent* event) override {
|
||||
InputCommon::GetKeyboard()->ReleaseKey(event->key());
|
||||
}
|
||||
|
||||
void mousePressEvent(QMouseEvent* event) override {
|
||||
if (event->source() == Qt::MouseEventSynthesizedBySystem)
|
||||
return; // touch input is handled in TouchBeginEvent
|
||||
|
||||
const auto pos{event->pos()};
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
const auto [x, y] = parent->ScaleTouch(pos);
|
||||
parent->TouchPressed(x, y);
|
||||
} else if (event->button() == Qt::RightButton) {
|
||||
InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
|
||||
}
|
||||
}
|
||||
|
||||
void mouseMoveEvent(QMouseEvent* event) override {
|
||||
if (event->source() == Qt::MouseEventSynthesizedBySystem)
|
||||
return; // touch input is handled in TouchUpdateEvent
|
||||
|
||||
const auto pos{event->pos()};
|
||||
const auto [x, y] = parent->ScaleTouch(pos);
|
||||
parent->TouchMoved(x, y);
|
||||
InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
|
||||
}
|
||||
|
||||
void mouseReleaseEvent(QMouseEvent* event) override {
|
||||
if (event->source() == Qt::MouseEventSynthesizedBySystem)
|
||||
return; // touch input is handled in TouchEndEvent
|
||||
|
||||
if (event->button() == Qt::LeftButton)
|
||||
parent->TouchReleased();
|
||||
else if (event->button() == Qt::RightButton)
|
||||
InputCommon::GetMotionEmu()->EndTilt();
|
||||
}
|
||||
|
||||
void DisablePainting() {
|
||||
do_painting = false;
|
||||
}
|
||||
|
||||
void EnablePainting() {
|
||||
do_painting = true;
|
||||
}
|
||||
@@ -186,7 +114,7 @@ private:
|
||||
};
|
||||
|
||||
GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
|
||||
: QWidget(parent), child(nullptr), context(nullptr), emu_thread(emu_thread) {
|
||||
: QWidget(parent), child(nullptr), emu_thread(emu_thread) {
|
||||
|
||||
setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
|
||||
.arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
|
||||
@@ -209,19 +137,19 @@ void GRenderWindow::moveContext() {
|
||||
auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr)
|
||||
? emu_thread
|
||||
: qApp->thread();
|
||||
context->moveToThread(thread);
|
||||
child->context()->moveToThread(thread);
|
||||
}
|
||||
|
||||
void GRenderWindow::SwapBuffers() {
|
||||
// In our multi-threaded QWidget use case we shouldn't need to call `makeCurrent`,
|
||||
// In our multi-threaded QGLWidget use case we shouldn't need to call `makeCurrent`,
|
||||
// since we never call `doneCurrent` in this thread.
|
||||
// However:
|
||||
// - The Qt debug runtime prints a bogus warning on the console if `makeCurrent` wasn't called
|
||||
// since the last time `swapBuffers` was executed;
|
||||
// - On macOS, if `makeCurrent` isn't called explicitely, resizing the buffer breaks.
|
||||
context->makeCurrent(child);
|
||||
child->makeCurrent();
|
||||
|
||||
context->swapBuffers(child);
|
||||
child->swapBuffers();
|
||||
if (!first_frame) {
|
||||
emit FirstFrameDisplayed();
|
||||
first_frame = true;
|
||||
@@ -229,11 +157,11 @@ void GRenderWindow::SwapBuffers() {
|
||||
}
|
||||
|
||||
void GRenderWindow::MakeCurrent() {
|
||||
context->makeCurrent(child);
|
||||
child->makeCurrent();
|
||||
}
|
||||
|
||||
void GRenderWindow::DoneCurrent() {
|
||||
context->doneCurrent();
|
||||
child->doneCurrent();
|
||||
}
|
||||
|
||||
void GRenderWindow::PollEvents() {}
|
||||
@@ -246,26 +174,14 @@ void GRenderWindow::PollEvents() {}
|
||||
void GRenderWindow::OnFramebufferSizeChanged() {
|
||||
// Screen changes potentially incur a change in screen DPI, hence we should update the
|
||||
// framebuffer size
|
||||
qreal pixelRatio = GetWindowPixelRatio();
|
||||
qreal pixelRatio = windowPixelRatio();
|
||||
unsigned width = child->QPaintDevice::width() * pixelRatio;
|
||||
unsigned height = child->QPaintDevice::height() * pixelRatio;
|
||||
UpdateCurrentFramebufferLayout(width, height);
|
||||
}
|
||||
|
||||
void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) {
|
||||
if (child) {
|
||||
child->keyPressEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void GRenderWindow::ForwardKeyReleaseEvent(QKeyEvent* event) {
|
||||
if (child) {
|
||||
child->keyReleaseEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void GRenderWindow::BackupGeometry() {
|
||||
geometry = ((QWidget*)this)->saveGeometry();
|
||||
geometry = ((QGLWidget*)this)->saveGeometry();
|
||||
}
|
||||
|
||||
void GRenderWindow::RestoreGeometry() {
|
||||
@@ -283,18 +199,18 @@ QByteArray GRenderWindow::saveGeometry() {
|
||||
// If we are a top-level widget, store the current geometry
|
||||
// otherwise, store the last backup
|
||||
if (parent() == nullptr)
|
||||
return ((QWidget*)this)->saveGeometry();
|
||||
return ((QGLWidget*)this)->saveGeometry();
|
||||
else
|
||||
return geometry;
|
||||
}
|
||||
|
||||
qreal GRenderWindow::GetWindowPixelRatio() const {
|
||||
qreal GRenderWindow::windowPixelRatio() const {
|
||||
// windowHandle() might not be accessible until the window is displayed to screen.
|
||||
return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f;
|
||||
}
|
||||
|
||||
std::pair<unsigned, unsigned> GRenderWindow::ScaleTouch(const QPointF pos) const {
|
||||
const qreal pixel_ratio = GetWindowPixelRatio();
|
||||
const qreal pixel_ratio = windowPixelRatio();
|
||||
return {static_cast<unsigned>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),
|
||||
static_cast<unsigned>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
|
||||
}
|
||||
@@ -304,6 +220,47 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
|
||||
QWidget::closeEvent(event);
|
||||
}
|
||||
|
||||
void GRenderWindow::keyPressEvent(QKeyEvent* event) {
|
||||
InputCommon::GetKeyboard()->PressKey(event->key());
|
||||
}
|
||||
|
||||
void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
|
||||
InputCommon::GetKeyboard()->ReleaseKey(event->key());
|
||||
}
|
||||
|
||||
void GRenderWindow::mousePressEvent(QMouseEvent* event) {
|
||||
if (event->source() == Qt::MouseEventSynthesizedBySystem)
|
||||
return; // touch input is handled in TouchBeginEvent
|
||||
|
||||
auto pos = event->pos();
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
const auto [x, y] = ScaleTouch(pos);
|
||||
this->TouchPressed(x, y);
|
||||
} else if (event->button() == Qt::RightButton) {
|
||||
InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
|
||||
}
|
||||
}
|
||||
|
||||
void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
|
||||
if (event->source() == Qt::MouseEventSynthesizedBySystem)
|
||||
return; // touch input is handled in TouchUpdateEvent
|
||||
|
||||
auto pos = event->pos();
|
||||
const auto [x, y] = ScaleTouch(pos);
|
||||
this->TouchMoved(x, y);
|
||||
InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
|
||||
}
|
||||
|
||||
void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
|
||||
if (event->source() == Qt::MouseEventSynthesizedBySystem)
|
||||
return; // touch input is handled in TouchEndEvent
|
||||
|
||||
if (event->button() == Qt::LeftButton)
|
||||
this->TouchReleased();
|
||||
else if (event->button() == Qt::RightButton)
|
||||
InputCommon::GetMotionEmu()->EndTilt();
|
||||
}
|
||||
|
||||
void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {
|
||||
// TouchBegin always has exactly one touch point, so take the .first()
|
||||
const auto [x, y] = ScaleTouch(event->touchPoints().first().pos());
|
||||
@@ -356,59 +313,34 @@ void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) {
|
||||
NotifyClientAreaSizeChanged(std::make_pair(width, height));
|
||||
}
|
||||
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
|
||||
return std::make_unique<GGLContext>(shared_context.get());
|
||||
}
|
||||
|
||||
void GRenderWindow::InitRenderTarget() {
|
||||
shared_context.reset();
|
||||
context.reset();
|
||||
if (child) {
|
||||
delete child;
|
||||
}
|
||||
|
||||
delete child;
|
||||
child = nullptr;
|
||||
|
||||
delete container;
|
||||
container = nullptr;
|
||||
|
||||
delete layout();
|
||||
if (layout()) {
|
||||
delete layout();
|
||||
}
|
||||
|
||||
first_frame = false;
|
||||
|
||||
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
|
||||
// WA_DontShowOnScreen, WA_DeleteOnClose
|
||||
QSurfaceFormat fmt;
|
||||
QGLFormat fmt;
|
||||
fmt.setVersion(4, 3);
|
||||
fmt.setProfile(QSurfaceFormat::CoreProfile);
|
||||
// TODO: expose a setting for buffer value (ie default/single/double/triple)
|
||||
fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
|
||||
shared_context = std::make_unique<QOpenGLContext>();
|
||||
shared_context->setFormat(fmt);
|
||||
shared_context->create();
|
||||
context = std::make_unique<QOpenGLContext>();
|
||||
context->setShareContext(shared_context.get());
|
||||
context->setFormat(fmt);
|
||||
context->create();
|
||||
fmt.setProfile(QGLFormat::CoreProfile);
|
||||
fmt.setSwapInterval(false);
|
||||
|
||||
child = new GGLWidgetInternal(this, shared_context.get());
|
||||
container = QWidget::createWindowContainer(child, this);
|
||||
// Requests a forward-compatible context, which is required to get a 3.2+ context on OS X
|
||||
fmt.setOption(QGL::NoDeprecatedFunctions);
|
||||
|
||||
child = new GGLWidgetInternal(fmt, this);
|
||||
QBoxLayout* layout = new QHBoxLayout(this);
|
||||
layout->addWidget(container);
|
||||
layout->setMargin(0);
|
||||
setLayout(layout);
|
||||
|
||||
// Reset minimum size to avoid unwanted resizes when this function is called for a second time.
|
||||
setMinimumSize(1, 1);
|
||||
|
||||
// Show causes the window to actually be created and the OpenGL context as well, but we don't
|
||||
// want the widget to be shown yet, so immediately hide it.
|
||||
show();
|
||||
hide();
|
||||
|
||||
resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
|
||||
child->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
|
||||
container->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
|
||||
layout->addWidget(child);
|
||||
layout->setMargin(0);
|
||||
setLayout(layout);
|
||||
|
||||
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <QGLWidget>
|
||||
#include <QImage>
|
||||
#include <QThread>
|
||||
#include <QWidget>
|
||||
#include "common/thread.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
@@ -21,8 +21,6 @@ class QTouchEvent;
|
||||
class GGLWidgetInternal;
|
||||
class GMainWindow;
|
||||
class GRenderWindow;
|
||||
class QSurface;
|
||||
class QOpenGLContext;
|
||||
|
||||
namespace VideoCore {
|
||||
enum class LoadCallbackStage;
|
||||
@@ -123,21 +121,25 @@ public:
|
||||
void MakeCurrent() override;
|
||||
void DoneCurrent() override;
|
||||
void PollEvents() override;
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
|
||||
|
||||
void ForwardKeyPressEvent(QKeyEvent* event);
|
||||
void ForwardKeyReleaseEvent(QKeyEvent* event);
|
||||
|
||||
void BackupGeometry();
|
||||
void RestoreGeometry();
|
||||
void restoreGeometry(const QByteArray& geometry); // overridden
|
||||
QByteArray saveGeometry(); // overridden
|
||||
|
||||
qreal GetWindowPixelRatio() const;
|
||||
std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const;
|
||||
qreal windowPixelRatio() const;
|
||||
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
void keyReleaseEvent(QKeyEvent* event) override;
|
||||
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
|
||||
bool event(QEvent* event) override;
|
||||
|
||||
void focusOutEvent(QFocusEvent* event) override;
|
||||
|
||||
void OnClientAreaResized(unsigned width, unsigned height);
|
||||
@@ -159,6 +161,7 @@ signals:
|
||||
void FirstFrameDisplayed();
|
||||
|
||||
private:
|
||||
std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const;
|
||||
void TouchBeginEvent(const QTouchEvent* event);
|
||||
void TouchUpdateEvent(const QTouchEvent* event);
|
||||
void TouchEndEvent();
|
||||
@@ -166,17 +169,11 @@ private:
|
||||
void OnMinimalClientAreaChangeRequest(
|
||||
const std::pair<unsigned, unsigned>& minimal_size) override;
|
||||
|
||||
QWidget* container = nullptr;
|
||||
GGLWidgetInternal* child = nullptr;
|
||||
GGLWidgetInternal* child;
|
||||
|
||||
QByteArray geometry;
|
||||
|
||||
EmuThread* emu_thread;
|
||||
// Context that backs the GGLWidgetInternal (and will be used by core to render)
|
||||
std::unique_ptr<QOpenGLContext> context;
|
||||
// Context that will be shared between all newly created contexts. This should never be made
|
||||
// current
|
||||
std::unique_ptr<QOpenGLContext> shared_context;
|
||||
|
||||
/// Temporary storage of the screenshot taken
|
||||
QImage screenshot_image;
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <QKeySequence>
|
||||
#include <QSettings>
|
||||
#include "common/file_util.h"
|
||||
#include "configure_input_simple.h"
|
||||
@@ -11,6 +9,7 @@
|
||||
#include "core/hle/service/hid/controllers/npad.h"
|
||||
#include "input_common/main.h"
|
||||
#include "yuzu/configuration/config.h"
|
||||
#include "yuzu/ui_settings.h"
|
||||
|
||||
Config::Config() {
|
||||
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
|
||||
@@ -18,6 +17,7 @@ Config::Config() {
|
||||
FileUtil::CreateFullPath(qt_config_loc);
|
||||
qt_config =
|
||||
std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
|
||||
|
||||
Reload();
|
||||
}
|
||||
|
||||
@@ -205,27 +205,6 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default
|
||||
Qt::Key_Control, Qt::Key_Shift, Qt::Key_AltGr, Qt::Key_ApplicationRight,
|
||||
};
|
||||
|
||||
// This shouldn't have anything except static initializers (no functions). So
|
||||
// QKeySequnce(...).toString() is NOT ALLOWED HERE.
|
||||
// This must be in alphabetical order according to action name as it must have the same order as
|
||||
// UISetting::values.shortcuts, which is alphabetically ordered.
|
||||
const std::array<UISettings::Shortcut, 15> Config::default_hotkeys{
|
||||
{{"Capture Screenshot", "Main Window", {"Ctrl+P", Qt::ApplicationShortcut}},
|
||||
{"Continue/Pause Emulation", "Main Window", {"F4", Qt::WindowShortcut}},
|
||||
{"Decrease Speed Limit", "Main Window", {"-", Qt::ApplicationShortcut}},
|
||||
{"Exit yuzu", "Main Window", {"Ctrl+Q", Qt::WindowShortcut}},
|
||||
{"Exit Fullscreen", "Main Window", {"Esc", Qt::WindowShortcut}},
|
||||
{"Fullscreen", "Main Window", {"F11", Qt::WindowShortcut}},
|
||||
{"Increase Speed Limit", "Main Window", {"+", Qt::ApplicationShortcut}},
|
||||
{"Load Amiibo", "Main Window", {"F2", Qt::ApplicationShortcut}},
|
||||
{"Load File", "Main Window", {"Ctrl+O", Qt::WindowShortcut}},
|
||||
{"Restart Emulation", "Main Window", {"F6", Qt::WindowShortcut}},
|
||||
{"Stop Emulation", "Main Window", {"F5", Qt::WindowShortcut}},
|
||||
{"Toggle Filter Bar", "Main Window", {"Ctrl+F", Qt::WindowShortcut}},
|
||||
{"Toggle Speed Limit", "Main Window", {"Ctrl+Z", Qt::ApplicationShortcut}},
|
||||
{"Toggle Status Bar", "Main Window", {"Ctrl+S", Qt::WindowShortcut}},
|
||||
{"Change Docked Mode", "Main Window", {"F10", Qt::ApplicationShortcut}}}};
|
||||
|
||||
void Config::ReadPlayerValues() {
|
||||
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
|
||||
auto& player = Settings::values.players[p];
|
||||
@@ -394,7 +373,6 @@ void Config::ReadValues() {
|
||||
ReadSetting("use_accurate_gpu_emulation", false).toBool();
|
||||
Settings::values.use_asynchronous_gpu_emulation =
|
||||
ReadSetting("use_asynchronous_gpu_emulation", false).toBool();
|
||||
Settings::values.force_30fps_mode = ReadSetting("force_30fps_mode", false).toBool();
|
||||
|
||||
Settings::values.bg_red = ReadSetting("bg_red", 0.0).toFloat();
|
||||
Settings::values.bg_green = ReadSetting("bg_green", 0.0).toFloat();
|
||||
@@ -524,21 +502,26 @@ void Config::ReadValues() {
|
||||
qt_config->beginGroup("Paths");
|
||||
UISettings::values.roms_path = ReadSetting("romsPath").toString();
|
||||
UISettings::values.symbols_path = ReadSetting("symbolsPath").toString();
|
||||
UISettings::values.game_directory_path = ReadSetting("gameListRootDir", ".").toString();
|
||||
UISettings::values.game_directory_deepscan = ReadSetting("gameListDeepScan", false).toBool();
|
||||
UISettings::values.gamedir = ReadSetting("gameListRootDir", ".").toString();
|
||||
UISettings::values.gamedir_deepscan = ReadSetting("gameListDeepScan", false).toBool();
|
||||
UISettings::values.recent_files = ReadSetting("recentFiles").toStringList();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Shortcuts");
|
||||
for (auto [name, group, shortcut] : default_hotkeys) {
|
||||
auto [keyseq, context] = shortcut;
|
||||
QStringList groups = qt_config->childGroups();
|
||||
for (auto group : groups) {
|
||||
qt_config->beginGroup(group);
|
||||
qt_config->beginGroup(name);
|
||||
UISettings::values.shortcuts.push_back(
|
||||
{name,
|
||||
group,
|
||||
{ReadSetting("KeySeq", keyseq).toString(), ReadSetting("Context", context).toInt()}});
|
||||
qt_config->endGroup();
|
||||
|
||||
QStringList hotkeys = qt_config->childGroups();
|
||||
for (auto hotkey : hotkeys) {
|
||||
qt_config->beginGroup(hotkey);
|
||||
UISettings::values.shortcuts.emplace_back(UISettings::Shortcut(
|
||||
group + "/" + hotkey,
|
||||
UISettings::ContextualShortcut(ReadSetting("KeySeq").toString(),
|
||||
ReadSetting("Context").toInt())));
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
qt_config->endGroup();
|
||||
}
|
||||
qt_config->endGroup();
|
||||
@@ -665,7 +648,6 @@ void Config::SaveValues() {
|
||||
WriteSetting("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation, false);
|
||||
WriteSetting("use_asynchronous_gpu_emulation", Settings::values.use_asynchronous_gpu_emulation,
|
||||
false);
|
||||
WriteSetting("force_30fps_mode", Settings::values.force_30fps_mode, false);
|
||||
|
||||
// Cast to double because Qt's written float values are not human-readable
|
||||
WriteSetting("bg_red", (double)Settings::values.bg_red, 0.0);
|
||||
@@ -770,22 +752,15 @@ void Config::SaveValues() {
|
||||
WriteSetting("romsPath", UISettings::values.roms_path);
|
||||
WriteSetting("symbolsPath", UISettings::values.symbols_path);
|
||||
WriteSetting("screenshotPath", UISettings::values.screenshot_path);
|
||||
WriteSetting("gameListRootDir", UISettings::values.game_directory_path, ".");
|
||||
WriteSetting("gameListDeepScan", UISettings::values.game_directory_deepscan, false);
|
||||
WriteSetting("gameListRootDir", UISettings::values.gamedir, ".");
|
||||
WriteSetting("gameListDeepScan", UISettings::values.gamedir_deepscan, false);
|
||||
WriteSetting("recentFiles", UISettings::values.recent_files);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Shortcuts");
|
||||
// Lengths of UISettings::values.shortcuts & default_hotkeys are same.
|
||||
// However, their ordering must also be the same.
|
||||
for (std::size_t i = 0; i < default_hotkeys.size(); i++) {
|
||||
auto [name, group, shortcut] = UISettings::values.shortcuts[i];
|
||||
qt_config->beginGroup(group);
|
||||
qt_config->beginGroup(name);
|
||||
WriteSetting("KeySeq", shortcut.first, default_hotkeys[i].shortcut.first);
|
||||
WriteSetting("Context", shortcut.second, default_hotkeys[i].shortcut.second);
|
||||
qt_config->endGroup();
|
||||
qt_config->endGroup();
|
||||
for (auto shortcut : UISettings::values.shortcuts) {
|
||||
WriteSetting(shortcut.first + "/KeySeq", shortcut.second.first);
|
||||
WriteSetting(shortcut.first + "/Context", shortcut.second.second);
|
||||
}
|
||||
qt_config->endGroup();
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <string>
|
||||
#include <QVariant>
|
||||
#include "core/settings.h"
|
||||
#include "yuzu/ui_settings.h"
|
||||
|
||||
class QSettings;
|
||||
|
||||
@@ -48,8 +47,6 @@ private:
|
||||
void WriteSetting(const QString& name, const QVariant& value);
|
||||
void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value);
|
||||
|
||||
static const std::array<UISettings::Shortcut, 15> default_hotkeys;
|
||||
|
||||
std::unique_ptr<QSettings> qt_config;
|
||||
std::string qt_config_loc;
|
||||
};
|
||||
|
||||
@@ -7,15 +7,9 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>382</width>
|
||||
<height>650</height>
|
||||
<height>241</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>650</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>yuzu Configuration</string>
|
||||
</property>
|
||||
@@ -68,11 +62,6 @@
|
||||
<string>Input</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="ConfigureHotkeys" name="hotkeysTab">
|
||||
<attribute name="title">
|
||||
<string>Hotkeys</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="ConfigureGraphics" name="graphicsTab">
|
||||
<attribute name="title">
|
||||
<string>Graphics</string>
|
||||
@@ -161,12 +150,6 @@
|
||||
<header>configuration/configure_input_simple.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ConfigureHotkeys</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>configuration/configure_hotkeys.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
|
||||
@@ -8,22 +8,20 @@
|
||||
#include "ui_configure.h"
|
||||
#include "yuzu/configuration/config.h"
|
||||
#include "yuzu/configuration/configure_dialog.h"
|
||||
#include "yuzu/configuration/configure_input_player.h"
|
||||
#include "yuzu/hotkeys.h"
|
||||
|
||||
ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry)
|
||||
: QDialog(parent), registry(registry), ui(new Ui::ConfigureDialog) {
|
||||
ConfigureDialog::ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry)
|
||||
: QDialog(parent), ui(new Ui::ConfigureDialog) {
|
||||
ui->setupUi(this);
|
||||
ui->hotkeysTab->Populate(registry);
|
||||
ui->generalTab->PopulateHotkeyList(registry);
|
||||
this->setConfiguration();
|
||||
this->PopulateSelectionList();
|
||||
connect(ui->selectorList, &QListWidget::itemSelectionChanged, this,
|
||||
&ConfigureDialog::UpdateVisibleTabs);
|
||||
adjustSize();
|
||||
ui->selectorList->setCurrentRow(0);
|
||||
|
||||
// Synchronise lists upon initialisation
|
||||
ui->hotkeysTab->EmitHotkeysChanged();
|
||||
adjustSize();
|
||||
|
||||
ui->selectorList->setCurrentRow(0);
|
||||
}
|
||||
|
||||
ConfigureDialog::~ConfigureDialog() = default;
|
||||
@@ -36,7 +34,6 @@ void ConfigureDialog::applyConfiguration() {
|
||||
ui->systemTab->applyConfiguration();
|
||||
ui->profileManagerTab->applyConfiguration();
|
||||
ui->inputTab->applyConfiguration();
|
||||
ui->hotkeysTab->applyConfiguration(registry);
|
||||
ui->graphicsTab->applyConfiguration();
|
||||
ui->audioTab->applyConfiguration();
|
||||
ui->debugTab->applyConfiguration();
|
||||
@@ -50,7 +47,7 @@ void ConfigureDialog::PopulateSelectionList() {
|
||||
{{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}},
|
||||
{tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}},
|
||||
{tr("Graphics"), {tr("Graphics")}},
|
||||
{tr("Controls"), {tr("Input"), tr("Hotkeys")}}}};
|
||||
{tr("Controls"), {tr("Input")}}}};
|
||||
|
||||
for (const auto& entry : items) {
|
||||
auto* const item = new QListWidgetItem(entry.first);
|
||||
@@ -69,7 +66,6 @@ void ConfigureDialog::UpdateVisibleTabs() {
|
||||
{tr("System"), ui->systemTab},
|
||||
{tr("Profiles"), ui->profileManagerTab},
|
||||
{tr("Input"), ui->inputTab},
|
||||
{tr("Hotkeys"), ui->hotkeysTab},
|
||||
{tr("Graphics"), ui->graphicsTab},
|
||||
{tr("Audio"), ui->audioTab},
|
||||
{tr("Debug"), ui->debugTab},
|
||||
|
||||
@@ -17,7 +17,7 @@ class ConfigureDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry);
|
||||
explicit ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry);
|
||||
~ConfigureDialog() override;
|
||||
|
||||
void applyConfiguration();
|
||||
@@ -28,5 +28,4 @@ private:
|
||||
void PopulateSelectionList();
|
||||
|
||||
std::unique_ptr<Ui::ConfigureDialog> ui;
|
||||
HotkeyRegistry& registry;
|
||||
};
|
||||
|
||||
@@ -28,15 +28,19 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
|
||||
ConfigureGeneral::~ConfigureGeneral() = default;
|
||||
|
||||
void ConfigureGeneral::setConfiguration() {
|
||||
ui->toggle_deepscan->setChecked(UISettings::values.game_directory_deepscan);
|
||||
ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan);
|
||||
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
|
||||
ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot);
|
||||
ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
|
||||
ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit);
|
||||
}
|
||||
|
||||
void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) {
|
||||
ui->widget->Populate(registry);
|
||||
}
|
||||
|
||||
void ConfigureGeneral::applyConfiguration() {
|
||||
UISettings::values.game_directory_deepscan = ui->toggle_deepscan->isChecked();
|
||||
UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked();
|
||||
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
|
||||
UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();
|
||||
UISettings::values.theme =
|
||||
|
||||
@@ -20,6 +20,7 @@ public:
|
||||
explicit ConfigureGeneral(QWidget* parent = nullptr);
|
||||
~ConfigureGeneral() override;
|
||||
|
||||
void PopulateHotkeyList(const HotkeyRegistry& registry);
|
||||
void applyConfiguration();
|
||||
|
||||
private:
|
||||
|
||||
@@ -97,6 +97,22 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="HotKeysGroupBox">
|
||||
<property name="title">
|
||||
<string>Hotkeys</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="HotKeysHorizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="HotKeysVerticalLayout">
|
||||
<item>
|
||||
<widget class="GHotkeysDialog" name="widget" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
@@ -114,6 +130,14 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>GHotkeysDialog</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>hotkeys.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
@@ -77,8 +77,6 @@ void ConfigureGraphics::setConfiguration() {
|
||||
ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation);
|
||||
ui->use_asynchronous_gpu_emulation->setEnabled(!Core::System::GetInstance().IsPoweredOn());
|
||||
ui->use_asynchronous_gpu_emulation->setChecked(Settings::values.use_asynchronous_gpu_emulation);
|
||||
ui->force_30fps_mode->setEnabled(!Core::System::GetInstance().IsPoweredOn());
|
||||
ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode);
|
||||
UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,
|
||||
Settings::values.bg_blue));
|
||||
}
|
||||
@@ -92,7 +90,6 @@ void ConfigureGraphics::applyConfiguration() {
|
||||
Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked();
|
||||
Settings::values.use_asynchronous_gpu_emulation =
|
||||
ui->use_asynchronous_gpu_emulation->isChecked();
|
||||
Settings::values.force_30fps_mode = ui->force_30fps_mode->isChecked();
|
||||
Settings::values.bg_red = static_cast<float>(bg_color.redF());
|
||||
Settings::values.bg_green = static_cast<float>(bg_color.greenF());
|
||||
Settings::values.bg_blue = static_cast<float>(bg_color.blueF());
|
||||
|
||||
@@ -70,13 +70,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="force_30fps_mode">
|
||||
<property name="text">
|
||||
<string>Force 30 FPS mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QStandardItemModel>
|
||||
#include "core/settings.h"
|
||||
#include "ui_configure_hotkeys.h"
|
||||
#include "yuzu/configuration/configure_hotkeys.h"
|
||||
#include "yuzu/hotkeys.h"
|
||||
#include "yuzu/util/sequence_dialog/sequence_dialog.h"
|
||||
|
||||
ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)
|
||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureHotkeys>()) {
|
||||
ui->setupUi(this);
|
||||
setFocusPolicy(Qt::ClickFocus);
|
||||
|
||||
model = new QStandardItemModel(this);
|
||||
model->setColumnCount(3);
|
||||
model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Context")});
|
||||
|
||||
connect(ui->hotkey_list, &QTreeView::doubleClicked, this, &ConfigureHotkeys::Configure);
|
||||
ui->hotkey_list->setModel(model);
|
||||
|
||||
// TODO(Kloen): Make context configurable as well (hiding the column for now)
|
||||
ui->hotkey_list->hideColumn(2);
|
||||
|
||||
ui->hotkey_list->setColumnWidth(0, 200);
|
||||
ui->hotkey_list->resizeColumnToContents(1);
|
||||
}
|
||||
|
||||
ConfigureHotkeys::~ConfigureHotkeys() = default;
|
||||
|
||||
void ConfigureHotkeys::EmitHotkeysChanged() {
|
||||
emit HotkeysChanged(GetUsedKeyList());
|
||||
}
|
||||
|
||||
QList<QKeySequence> ConfigureHotkeys::GetUsedKeyList() const {
|
||||
QList<QKeySequence> list;
|
||||
for (int r = 0; r < model->rowCount(); r++) {
|
||||
const QStandardItem* parent = model->item(r, 0);
|
||||
for (int r2 = 0; r2 < parent->rowCount(); r2++) {
|
||||
const QStandardItem* keyseq = parent->child(r2, 1);
|
||||
list << QKeySequence::fromString(keyseq->text(), QKeySequence::NativeText);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) {
|
||||
for (const auto& group : registry.hotkey_groups) {
|
||||
auto* parent_item = new QStandardItem(group.first);
|
||||
parent_item->setEditable(false);
|
||||
for (const auto& hotkey : group.second) {
|
||||
auto* action = new QStandardItem(hotkey.first);
|
||||
auto* keyseq =
|
||||
new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText));
|
||||
action->setEditable(false);
|
||||
keyseq->setEditable(false);
|
||||
parent_item->appendRow({action, keyseq});
|
||||
}
|
||||
model->appendRow(parent_item);
|
||||
}
|
||||
|
||||
ui->hotkey_list->expandAll();
|
||||
}
|
||||
|
||||
void ConfigureHotkeys::Configure(QModelIndex index) {
|
||||
if (index.parent() == QModelIndex())
|
||||
return;
|
||||
|
||||
index = index.sibling(index.row(), 1);
|
||||
auto* model = ui->hotkey_list->model();
|
||||
auto previous_key = model->data(index);
|
||||
|
||||
auto* hotkey_dialog = new SequenceDialog;
|
||||
int return_code = hotkey_dialog->exec();
|
||||
|
||||
auto key_sequence = hotkey_dialog->GetSequence();
|
||||
|
||||
if (return_code == QDialog::Rejected || key_sequence.isEmpty())
|
||||
return;
|
||||
|
||||
if (IsUsedKey(key_sequence) && key_sequence != QKeySequence(previous_key.toString())) {
|
||||
QMessageBox::critical(this, tr("Error in inputted key"),
|
||||
tr("You're using a key that's already bound."));
|
||||
} else {
|
||||
model->setData(index, key_sequence.toString(QKeySequence::NativeText));
|
||||
EmitHotkeysChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) {
|
||||
return GetUsedKeyList().contains(key_sequence);
|
||||
}
|
||||
|
||||
void ConfigureHotkeys::applyConfiguration(HotkeyRegistry& registry) {
|
||||
for (int key_id = 0; key_id < model->rowCount(); key_id++) {
|
||||
const QStandardItem* parent = model->item(key_id, 0);
|
||||
for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) {
|
||||
const QStandardItem* action = parent->child(key_column_id, 0);
|
||||
const QStandardItem* keyseq = parent->child(key_column_id, 1);
|
||||
for (auto& [group, sub_actions] : registry.hotkey_groups) {
|
||||
if (group != parent->text())
|
||||
continue;
|
||||
for (auto& [action_name, hotkey] : sub_actions) {
|
||||
if (action_name != action->text())
|
||||
continue;
|
||||
hotkey.keyseq = QKeySequence(keyseq->text());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registry.SaveHotkeys();
|
||||
Settings::Apply();
|
||||
}
|
||||
|
||||
void ConfigureHotkeys::retranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QWidget>
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Ui {
|
||||
class ConfigureHotkeys;
|
||||
}
|
||||
|
||||
class HotkeyRegistry;
|
||||
class QStandardItemModel;
|
||||
|
||||
class ConfigureHotkeys : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ConfigureHotkeys(QWidget* parent = nullptr);
|
||||
~ConfigureHotkeys() override;
|
||||
|
||||
void applyConfiguration(HotkeyRegistry& registry);
|
||||
void retranslateUi();
|
||||
|
||||
void EmitHotkeysChanged();
|
||||
|
||||
/**
|
||||
* Populates the hotkey list widget using data from the provided registry.
|
||||
* Called everytime the Configure dialog is opened.
|
||||
* @param registry The HotkeyRegistry whose data is used to populate the list.
|
||||
*/
|
||||
void Populate(const HotkeyRegistry& registry);
|
||||
|
||||
signals:
|
||||
void HotkeysChanged(QList<QKeySequence> new_key_list);
|
||||
|
||||
private:
|
||||
void Configure(QModelIndex index);
|
||||
bool IsUsedKey(QKeySequence key_sequence);
|
||||
QList<QKeySequence> GetUsedKeyList() const;
|
||||
|
||||
std::unique_ptr<Ui::ConfigureHotkeys> ui;
|
||||
|
||||
QStandardItemModel* model;
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ConfigureHotkeys</class>
|
||||
<widget class="QWidget" name="ConfigureHotkeys">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>363</width>
|
||||
<height>388</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Hotkey Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Double-click on a binding to change it.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeView" name="hotkey_list">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
516
src/yuzu/debugger/graphics/graphics_surface.cpp
Normal file
516
src/yuzu/debugger/graphics/graphics_surface.cpp
Normal file
@@ -0,0 +1,516 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QBoxLayout>
|
||||
#include <QComboBox>
|
||||
#include <QDebug>
|
||||
#include <QFileDialog>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QMouseEvent>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QSpinBox>
|
||||
#include "common/vector_math.h"
|
||||
#include "core/core.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
#include "yuzu/debugger/graphics/graphics_surface.h"
|
||||
#include "yuzu/util/spinbox.h"
|
||||
|
||||
static Tegra::Texture::TextureFormat ConvertToTextureFormat(
|
||||
Tegra::RenderTargetFormat render_target_format) {
|
||||
switch (render_target_format) {
|
||||
case Tegra::RenderTargetFormat::RGBA8_UNORM:
|
||||
return Tegra::Texture::TextureFormat::A8R8G8B8;
|
||||
case Tegra::RenderTargetFormat::RGB10_A2_UNORM:
|
||||
return Tegra::Texture::TextureFormat::A2B10G10R10;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented RT format");
|
||||
return Tegra::Texture::TextureFormat::A8R8G8B8;
|
||||
}
|
||||
}
|
||||
|
||||
SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_)
|
||||
: QLabel(parent), surface_widget(surface_widget_) {}
|
||||
|
||||
SurfacePicture::~SurfacePicture() = default;
|
||||
|
||||
void SurfacePicture::mousePressEvent(QMouseEvent* event) {
|
||||
// Only do something while the left mouse button is held down
|
||||
if (!(event->buttons() & Qt::LeftButton))
|
||||
return;
|
||||
|
||||
if (pixmap() == nullptr)
|
||||
return;
|
||||
|
||||
if (surface_widget)
|
||||
surface_widget->Pick(event->x() * pixmap()->width() / width(),
|
||||
event->y() * pixmap()->height() / height());
|
||||
}
|
||||
|
||||
void SurfacePicture::mouseMoveEvent(QMouseEvent* event) {
|
||||
// We also want to handle the event if the user moves the mouse while holding down the LMB
|
||||
mousePressEvent(event);
|
||||
}
|
||||
|
||||
GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context,
|
||||
QWidget* parent)
|
||||
: BreakPointObserverDock(debug_context, tr("Maxwell Surface Viewer"), parent),
|
||||
surface_source(Source::RenderTarget0) {
|
||||
setObjectName("MaxwellSurface");
|
||||
|
||||
surface_source_list = new QComboBox;
|
||||
surface_source_list->addItem(tr("Render Target 0"));
|
||||
surface_source_list->addItem(tr("Render Target 1"));
|
||||
surface_source_list->addItem(tr("Render Target 2"));
|
||||
surface_source_list->addItem(tr("Render Target 3"));
|
||||
surface_source_list->addItem(tr("Render Target 4"));
|
||||
surface_source_list->addItem(tr("Render Target 5"));
|
||||
surface_source_list->addItem(tr("Render Target 6"));
|
||||
surface_source_list->addItem(tr("Render Target 7"));
|
||||
surface_source_list->addItem(tr("Z Buffer"));
|
||||
surface_source_list->addItem(tr("Custom"));
|
||||
surface_source_list->setCurrentIndex(static_cast<int>(surface_source));
|
||||
|
||||
surface_address_control = new CSpinBox;
|
||||
surface_address_control->SetBase(16);
|
||||
surface_address_control->SetRange(0, 0x7FFFFFFFFFFFFFFF);
|
||||
surface_address_control->SetPrefix("0x");
|
||||
|
||||
unsigned max_dimension = 16384; // TODO: Find actual maximum
|
||||
|
||||
surface_width_control = new QSpinBox;
|
||||
surface_width_control->setRange(0, max_dimension);
|
||||
|
||||
surface_height_control = new QSpinBox;
|
||||
surface_height_control->setRange(0, max_dimension);
|
||||
|
||||
surface_picker_x_control = new QSpinBox;
|
||||
surface_picker_x_control->setRange(0, max_dimension - 1);
|
||||
|
||||
surface_picker_y_control = new QSpinBox;
|
||||
surface_picker_y_control->setRange(0, max_dimension - 1);
|
||||
|
||||
// clang-format off
|
||||
// Color formats sorted by Maxwell texture format index
|
||||
const QStringList surface_formats{
|
||||
tr("None"),
|
||||
QStringLiteral("R32_G32_B32_A32"),
|
||||
QStringLiteral("R32_G32_B32"),
|
||||
QStringLiteral("R16_G16_B16_A16"),
|
||||
QStringLiteral("R32_G32"),
|
||||
QStringLiteral("R32_B24G8"),
|
||||
QStringLiteral("ETC2_RGB"),
|
||||
QStringLiteral("X8B8G8R8"),
|
||||
QStringLiteral("A8R8G8B8"),
|
||||
QStringLiteral("A2B10G10R10"),
|
||||
QStringLiteral("ETC2_RGB_PTA"),
|
||||
QStringLiteral("ETC2_RGBA"),
|
||||
QStringLiteral("R16_G16"),
|
||||
QStringLiteral("G8R24"),
|
||||
QStringLiteral("G24R8"),
|
||||
QStringLiteral("R32"),
|
||||
QStringLiteral("BC6H_SF16"),
|
||||
QStringLiteral("BC6H_UF16"),
|
||||
QStringLiteral("A4B4G4R4"),
|
||||
QStringLiteral("A5B5G5R1"),
|
||||
QStringLiteral("A1B5G5R5"),
|
||||
QStringLiteral("B5G6R5"),
|
||||
QStringLiteral("B6G5R5"),
|
||||
QStringLiteral("BC7U"),
|
||||
QStringLiteral("G8R8"),
|
||||
QStringLiteral("EAC"),
|
||||
QStringLiteral("EACX2"),
|
||||
QStringLiteral("R16"),
|
||||
QStringLiteral("Y8_VIDEO"),
|
||||
QStringLiteral("R8"),
|
||||
QStringLiteral("G4R4"),
|
||||
QStringLiteral("R1"),
|
||||
QStringLiteral("E5B9G9R9_SHAREDEXP"),
|
||||
QStringLiteral("BF10GF11RF11"),
|
||||
QStringLiteral("G8B8G8R8"),
|
||||
QStringLiteral("B8G8R8G8"),
|
||||
QStringLiteral("DXT1"),
|
||||
QStringLiteral("DXT23"),
|
||||
QStringLiteral("DXT45"),
|
||||
QStringLiteral("DXN1"),
|
||||
QStringLiteral("DXN2"),
|
||||
QStringLiteral("Z24S8"),
|
||||
QStringLiteral("X8Z24"),
|
||||
QStringLiteral("S8Z24"),
|
||||
QStringLiteral("X4V4Z24__COV4R4V"),
|
||||
QStringLiteral("X4V4Z24__COV8R8V"),
|
||||
QStringLiteral("V8Z24__COV4R12V"),
|
||||
QStringLiteral("ZF32"),
|
||||
QStringLiteral("ZF32_X24S8"),
|
||||
QStringLiteral("X8Z24_X20V4S8__COV4R4V"),
|
||||
QStringLiteral("X8Z24_X20V4S8__COV8R8V"),
|
||||
QStringLiteral("ZF32_X20V4X8__COV4R4V"),
|
||||
QStringLiteral("ZF32_X20V4X8__COV8R8V"),
|
||||
QStringLiteral("ZF32_X20V4S8__COV4R4V"),
|
||||
QStringLiteral("ZF32_X20V4S8__COV8R8V"),
|
||||
QStringLiteral("X8Z24_X16V8S8__COV4R12V"),
|
||||
QStringLiteral("ZF32_X16V8X8__COV4R12V"),
|
||||
QStringLiteral("ZF32_X16V8S8__COV4R12V"),
|
||||
QStringLiteral("Z16"),
|
||||
QStringLiteral("V8Z24__COV8R24V"),
|
||||
QStringLiteral("X8Z24_X16V8S8__COV8R24V"),
|
||||
QStringLiteral("ZF32_X16V8X8__COV8R24V"),
|
||||
QStringLiteral("ZF32_X16V8S8__COV8R24V"),
|
||||
QStringLiteral("ASTC_2D_4X4"),
|
||||
QStringLiteral("ASTC_2D_5X5"),
|
||||
QStringLiteral("ASTC_2D_6X6"),
|
||||
QStringLiteral("ASTC_2D_8X8"),
|
||||
QStringLiteral("ASTC_2D_10X10"),
|
||||
QStringLiteral("ASTC_2D_12X12"),
|
||||
QStringLiteral("ASTC_2D_5X4"),
|
||||
QStringLiteral("ASTC_2D_6X5"),
|
||||
QStringLiteral("ASTC_2D_8X6"),
|
||||
QStringLiteral("ASTC_2D_10X8"),
|
||||
QStringLiteral("ASTC_2D_12X10"),
|
||||
QStringLiteral("ASTC_2D_8X5"),
|
||||
QStringLiteral("ASTC_2D_10X5"),
|
||||
QStringLiteral("ASTC_2D_10X6"),
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
surface_format_control = new QComboBox;
|
||||
surface_format_control->addItems(surface_formats);
|
||||
|
||||
surface_info_label = new QLabel();
|
||||
surface_info_label->setWordWrap(true);
|
||||
|
||||
surface_picture_label = new SurfacePicture(0, this);
|
||||
surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
surface_picture_label->setScaledContents(false);
|
||||
|
||||
auto scroll_area = new QScrollArea();
|
||||
scroll_area->setBackgroundRole(QPalette::Dark);
|
||||
scroll_area->setWidgetResizable(false);
|
||||
scroll_area->setWidget(surface_picture_label);
|
||||
|
||||
save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save"));
|
||||
|
||||
// Connections
|
||||
connect(this, &GraphicsSurfaceWidget::Update, this, &GraphicsSurfaceWidget::OnUpdate);
|
||||
connect(surface_source_list, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||
&GraphicsSurfaceWidget::OnSurfaceSourceChanged);
|
||||
connect(surface_address_control, &CSpinBox::ValueChanged, this,
|
||||
&GraphicsSurfaceWidget::OnSurfaceAddressChanged);
|
||||
connect(surface_width_control, qOverload<int>(&QSpinBox::valueChanged), this,
|
||||
&GraphicsSurfaceWidget::OnSurfaceWidthChanged);
|
||||
connect(surface_height_control, qOverload<int>(&QSpinBox::valueChanged), this,
|
||||
&GraphicsSurfaceWidget::OnSurfaceHeightChanged);
|
||||
connect(surface_format_control, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||
&GraphicsSurfaceWidget::OnSurfaceFormatChanged);
|
||||
connect(surface_picker_x_control, qOverload<int>(&QSpinBox::valueChanged), this,
|
||||
&GraphicsSurfaceWidget::OnSurfacePickerXChanged);
|
||||
connect(surface_picker_y_control, qOverload<int>(&QSpinBox::valueChanged), this,
|
||||
&GraphicsSurfaceWidget::OnSurfacePickerYChanged);
|
||||
connect(save_surface, &QPushButton::clicked, this, &GraphicsSurfaceWidget::SaveSurface);
|
||||
|
||||
auto main_widget = new QWidget;
|
||||
auto main_layout = new QVBoxLayout;
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Source:")));
|
||||
sub_layout->addWidget(surface_source_list);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("GPU Address:")));
|
||||
sub_layout->addWidget(surface_address_control);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Width:")));
|
||||
sub_layout->addWidget(surface_width_control);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Height:")));
|
||||
sub_layout->addWidget(surface_height_control);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Format:")));
|
||||
sub_layout->addWidget(surface_format_control);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
main_layout->addWidget(scroll_area);
|
||||
|
||||
auto info_layout = new QHBoxLayout;
|
||||
{
|
||||
auto xy_layout = new QVBoxLayout;
|
||||
{
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("X:")));
|
||||
sub_layout->addWidget(surface_picker_x_control);
|
||||
xy_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Y:")));
|
||||
sub_layout->addWidget(surface_picker_y_control);
|
||||
xy_layout->addLayout(sub_layout);
|
||||
}
|
||||
}
|
||||
info_layout->addLayout(xy_layout);
|
||||
surface_info_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
info_layout->addWidget(surface_info_label);
|
||||
}
|
||||
main_layout->addLayout(info_layout);
|
||||
|
||||
main_layout->addWidget(save_surface);
|
||||
main_widget->setLayout(main_layout);
|
||||
setWidget(main_widget);
|
||||
|
||||
// Load current data - TODO: Make sure this works when emulation is not running
|
||||
if (debug_context && debug_context->at_breakpoint) {
|
||||
emit Update();
|
||||
widget()->setEnabled(debug_context->at_breakpoint);
|
||||
} else {
|
||||
widget()->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) {
|
||||
emit Update();
|
||||
widget()->setEnabled(true);
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnResumed() {
|
||||
widget()->setEnabled(false);
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) {
|
||||
surface_source = static_cast<Source>(new_value);
|
||||
emit Update();
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) {
|
||||
if (surface_address != new_value) {
|
||||
surface_address = static_cast<GPUVAddr>(new_value);
|
||||
|
||||
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
||||
emit Update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value) {
|
||||
if (surface_width != static_cast<unsigned>(new_value)) {
|
||||
surface_width = static_cast<unsigned>(new_value);
|
||||
|
||||
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
||||
emit Update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value) {
|
||||
if (surface_height != static_cast<unsigned>(new_value)) {
|
||||
surface_height = static_cast<unsigned>(new_value);
|
||||
|
||||
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
||||
emit Update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value) {
|
||||
if (surface_format != static_cast<Tegra::Texture::TextureFormat>(new_value)) {
|
||||
surface_format = static_cast<Tegra::Texture::TextureFormat>(new_value);
|
||||
|
||||
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
||||
emit Update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value) {
|
||||
if (surface_picker_x != new_value) {
|
||||
surface_picker_x = new_value;
|
||||
Pick(surface_picker_x, surface_picker_y);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value) {
|
||||
if (surface_picker_y != new_value) {
|
||||
surface_picker_y = new_value;
|
||||
Pick(surface_picker_x, surface_picker_y);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::Pick(int x, int y) {
|
||||
surface_picker_x_control->setValue(x);
|
||||
surface_picker_y_control->setValue(y);
|
||||
|
||||
if (x < 0 || x >= static_cast<int>(surface_width) || y < 0 ||
|
||||
y >= static_cast<int>(surface_height)) {
|
||||
surface_info_label->setText(tr("Pixel out of bounds"));
|
||||
surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
return;
|
||||
}
|
||||
|
||||
surface_info_label->setText(QString("Raw: <Unimplemented>\n(%1)").arg("<Unimplemented>"));
|
||||
surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnUpdate() {
|
||||
auto& gpu = Core::System::GetInstance().GPU();
|
||||
|
||||
QPixmap pixmap;
|
||||
|
||||
switch (surface_source) {
|
||||
case Source::RenderTarget0:
|
||||
case Source::RenderTarget1:
|
||||
case Source::RenderTarget2:
|
||||
case Source::RenderTarget3:
|
||||
case Source::RenderTarget4:
|
||||
case Source::RenderTarget5:
|
||||
case Source::RenderTarget6:
|
||||
case Source::RenderTarget7: {
|
||||
// TODO: Store a reference to the registers in the debug context instead of accessing them
|
||||
// directly...
|
||||
|
||||
const auto& registers = gpu.Maxwell3D().regs;
|
||||
const auto& rt = registers.rt[static_cast<std::size_t>(surface_source) -
|
||||
static_cast<std::size_t>(Source::RenderTarget0)];
|
||||
|
||||
surface_address = rt.Address();
|
||||
surface_width = rt.width;
|
||||
surface_height = rt.height;
|
||||
if (rt.format != Tegra::RenderTargetFormat::NONE) {
|
||||
surface_format = ConvertToTextureFormat(rt.format);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Source::Custom: {
|
||||
// Keep user-specified values
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
qDebug() << "Unknown surface source " << static_cast<int>(surface_source);
|
||||
break;
|
||||
}
|
||||
|
||||
surface_address_control->SetValue(surface_address);
|
||||
surface_width_control->setValue(surface_width);
|
||||
surface_height_control->setValue(surface_height);
|
||||
surface_format_control->setCurrentIndex(static_cast<int>(surface_format));
|
||||
|
||||
if (surface_address == 0) {
|
||||
surface_picture_label->hide();
|
||||
surface_info_label->setText(tr("(invalid surface address)"));
|
||||
surface_info_label->setAlignment(Qt::AlignCenter);
|
||||
surface_picker_x_control->setEnabled(false);
|
||||
surface_picker_y_control->setEnabled(false);
|
||||
save_surface->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Implement a good way to visualize alpha components!
|
||||
|
||||
QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32);
|
||||
|
||||
// TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles.
|
||||
// Needs to be fixed if we plan to use this feature more, otherwise we may remove it.
|
||||
auto unswizzled_data = Tegra::Texture::UnswizzleTexture(
|
||||
gpu.MemoryManager().GetPointer(surface_address), 1, 1,
|
||||
Tegra::Texture::BytesPerPixel(surface_format), surface_width, surface_height, 1U);
|
||||
|
||||
auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format,
|
||||
surface_width, surface_height);
|
||||
|
||||
surface_picture_label->show();
|
||||
|
||||
for (unsigned int y = 0; y < surface_height; ++y) {
|
||||
for (unsigned int x = 0; x < surface_width; ++x) {
|
||||
Common::Vec4<u8> color;
|
||||
color[0] = texture_data[x + y * surface_width + 0];
|
||||
color[1] = texture_data[x + y * surface_width + 1];
|
||||
color[2] = texture_data[x + y * surface_width + 2];
|
||||
color[3] = texture_data[x + y * surface_width + 3];
|
||||
decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a()));
|
||||
}
|
||||
}
|
||||
|
||||
pixmap = QPixmap::fromImage(decoded_image);
|
||||
surface_picture_label->setPixmap(pixmap);
|
||||
surface_picture_label->resize(pixmap.size());
|
||||
|
||||
// Update the info with pixel data
|
||||
surface_picker_x_control->setEnabled(true);
|
||||
surface_picker_y_control->setEnabled(true);
|
||||
Pick(surface_picker_x, surface_picker_y);
|
||||
|
||||
// Enable saving the converted pixmap to file
|
||||
save_surface->setEnabled(true);
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::SaveSurface() {
|
||||
const QString png_filter = tr("Portable Network Graphic (*.png)");
|
||||
const QString bin_filter = tr("Binary data (*.bin)");
|
||||
|
||||
QString selected_filter;
|
||||
const QString filename = QFileDialog::getSaveFileName(
|
||||
this, tr("Save Surface"),
|
||||
QStringLiteral("texture-0x%1.png").arg(QString::number(surface_address, 16)),
|
||||
QStringLiteral("%1;;%2").arg(png_filter, bin_filter), &selected_filter);
|
||||
|
||||
if (filename.isEmpty()) {
|
||||
// If the user canceled the dialog, don't save anything.
|
||||
return;
|
||||
}
|
||||
|
||||
if (selected_filter == png_filter) {
|
||||
const QPixmap* const pixmap = surface_picture_label->pixmap();
|
||||
ASSERT_MSG(pixmap != nullptr, "No pixmap set");
|
||||
|
||||
QFile file{filename};
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
QMessageBox::warning(this, tr("Error"), tr("Failed to open file '%1'").arg(filename));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pixmap->save(&file, "PNG")) {
|
||||
QMessageBox::warning(this, tr("Error"),
|
||||
tr("Failed to save surface data to file '%1'").arg(filename));
|
||||
}
|
||||
} else if (selected_filter == bin_filter) {
|
||||
auto& gpu = Core::System::GetInstance().GPU();
|
||||
const std::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address);
|
||||
|
||||
const u8* const buffer = Memory::GetPointer(*address);
|
||||
ASSERT_MSG(buffer != nullptr, "Memory not accessible");
|
||||
|
||||
QFile file{filename};
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
QMessageBox::warning(this, tr("Error"), tr("Failed to open file '%1'").arg(filename));
|
||||
return;
|
||||
}
|
||||
|
||||
const int size =
|
||||
surface_width * surface_height * Tegra::Texture::BytesPerPixel(surface_format);
|
||||
const QByteArray data(reinterpret_cast<const char*>(buffer), size);
|
||||
if (file.write(data) != data.size()) {
|
||||
QMessageBox::warning(
|
||||
this, tr("Error"),
|
||||
tr("Failed to completely write surface data to file. The saved data will "
|
||||
"likely be corrupt."));
|
||||
}
|
||||
} else {
|
||||
UNREACHABLE_MSG("Unhandled filter selected");
|
||||
}
|
||||
}
|
||||
96
src/yuzu/debugger/graphics/graphics_surface.h
Normal file
96
src/yuzu/debugger/graphics/graphics_surface.h
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h"
|
||||
|
||||
class QComboBox;
|
||||
class QSpinBox;
|
||||
class CSpinBox;
|
||||
|
||||
class GraphicsSurfaceWidget;
|
||||
|
||||
class SurfacePicture : public QLabel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SurfacePicture(QWidget* parent = nullptr,
|
||||
GraphicsSurfaceWidget* surface_widget = nullptr);
|
||||
~SurfacePicture() override;
|
||||
|
||||
protected slots:
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
|
||||
private:
|
||||
GraphicsSurfaceWidget* surface_widget;
|
||||
};
|
||||
|
||||
class GraphicsSurfaceWidget : public BreakPointObserverDock {
|
||||
Q_OBJECT
|
||||
|
||||
using Event = Tegra::DebugContext::Event;
|
||||
|
||||
enum class Source {
|
||||
RenderTarget0 = 0,
|
||||
RenderTarget1 = 1,
|
||||
RenderTarget2 = 2,
|
||||
RenderTarget3 = 3,
|
||||
RenderTarget4 = 4,
|
||||
RenderTarget5 = 5,
|
||||
RenderTarget6 = 6,
|
||||
RenderTarget7 = 7,
|
||||
ZBuffer = 8,
|
||||
Custom = 9,
|
||||
};
|
||||
|
||||
public:
|
||||
explicit GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context,
|
||||
QWidget* parent = nullptr);
|
||||
void Pick(int x, int y);
|
||||
|
||||
public slots:
|
||||
void OnSurfaceSourceChanged(int new_value);
|
||||
void OnSurfaceAddressChanged(qint64 new_value);
|
||||
void OnSurfaceWidthChanged(int new_value);
|
||||
void OnSurfaceHeightChanged(int new_value);
|
||||
void OnSurfaceFormatChanged(int new_value);
|
||||
void OnSurfacePickerXChanged(int new_value);
|
||||
void OnSurfacePickerYChanged(int new_value);
|
||||
void OnUpdate();
|
||||
|
||||
signals:
|
||||
void Update();
|
||||
|
||||
private:
|
||||
void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) override;
|
||||
void OnResumed() override;
|
||||
|
||||
void SaveSurface();
|
||||
|
||||
QComboBox* surface_source_list;
|
||||
CSpinBox* surface_address_control;
|
||||
QSpinBox* surface_width_control;
|
||||
QSpinBox* surface_height_control;
|
||||
QComboBox* surface_format_control;
|
||||
|
||||
SurfacePicture* surface_picture_label;
|
||||
QSpinBox* surface_picker_x_control;
|
||||
QSpinBox* surface_picker_y_control;
|
||||
QLabel* surface_info_label;
|
||||
QPushButton* save_surface;
|
||||
|
||||
Source surface_source;
|
||||
GPUVAddr surface_address;
|
||||
unsigned surface_width;
|
||||
unsigned surface_height;
|
||||
Tegra::Texture::TextureFormat surface_format;
|
||||
int surface_picker_x = 0;
|
||||
int surface_picker_y = 0;
|
||||
};
|
||||
@@ -18,7 +18,6 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
#include "yuzu/game_list.h"
|
||||
#include "yuzu/game_list_p.h"
|
||||
@@ -194,9 +193,8 @@ void GameList::onFilterCloseClicked() {
|
||||
main_window->filterBarSetChecked(false);
|
||||
}
|
||||
|
||||
GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvider* provider,
|
||||
GMainWindow* parent)
|
||||
: QWidget{parent}, vfs(std::move(vfs)), provider(provider) {
|
||||
GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent)
|
||||
: QWidget{parent}, vfs(std::move(vfs)) {
|
||||
watcher = new QFileSystemWatcher(this);
|
||||
connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
|
||||
|
||||
@@ -434,8 +432,7 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
|
||||
|
||||
emit ShouldCancelWorker();
|
||||
|
||||
GameListWorker* worker =
|
||||
new GameListWorker(vfs, provider, dir_path, deep_scan, compatibility_list);
|
||||
GameListWorker* worker = new GameListWorker(vfs, dir_path, deep_scan, compatibility_list);
|
||||
|
||||
connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
|
||||
connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating,
|
||||
@@ -467,10 +464,9 @@ void GameList::LoadInterfaceLayout() {
|
||||
const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci", "nsp"};
|
||||
|
||||
void GameList::RefreshGameDirectory() {
|
||||
if (!UISettings::values.game_directory_path.isEmpty() && current_worker != nullptr) {
|
||||
if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) {
|
||||
LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
|
||||
search_field->clear();
|
||||
PopulateAsync(UISettings::values.game_directory_path,
|
||||
UISettings::values.game_directory_deepscan);
|
||||
PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +26,8 @@ class GameListSearchField;
|
||||
class GMainWindow;
|
||||
|
||||
namespace FileSys {
|
||||
class ManualContentProvider;
|
||||
class VfsFilesystem;
|
||||
} // namespace FileSys
|
||||
}
|
||||
|
||||
enum class GameListOpenTarget {
|
||||
SaveData,
|
||||
@@ -48,8 +47,7 @@ public:
|
||||
COLUMN_COUNT, // Number of columns
|
||||
};
|
||||
|
||||
explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs,
|
||||
FileSys::ManualContentProvider* provider, GMainWindow* parent = nullptr);
|
||||
explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs, GMainWindow* parent = nullptr);
|
||||
~GameList() override;
|
||||
|
||||
void clearFilter();
|
||||
@@ -88,7 +86,6 @@ private:
|
||||
void RefreshGameDirectory();
|
||||
|
||||
std::shared_ptr<FileSys::VfsFilesystem> vfs;
|
||||
FileSys::ManualContentProvider* provider;
|
||||
GameListSearchField* search_field;
|
||||
GMainWindow* main_window = nullptr;
|
||||
QVBoxLayout* layout = nullptr;
|
||||
|
||||
@@ -95,7 +95,7 @@ public:
|
||||
if (row2.isEmpty())
|
||||
return row1;
|
||||
|
||||
return QString(row1 + "\n " + row2);
|
||||
return row1 + "\n " + row2;
|
||||
}
|
||||
|
||||
return GameListItem::data(role);
|
||||
|
||||
@@ -12,15 +12,12 @@
|
||||
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
@@ -122,25 +119,20 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs,
|
||||
FileSys::ManualContentProvider* provider, QString dir_path,
|
||||
bool deep_scan, const CompatibilityList& compatibility_list)
|
||||
: vfs(std::move(vfs)), provider(provider), dir_path(std::move(dir_path)), deep_scan(deep_scan),
|
||||
GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan,
|
||||
const CompatibilityList& compatibility_list)
|
||||
: vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan),
|
||||
compatibility_list(compatibility_list) {}
|
||||
|
||||
GameListWorker::~GameListWorker() = default;
|
||||
|
||||
void GameListWorker::AddTitlesToGameList() {
|
||||
const auto& cache = dynamic_cast<FileSys::ContentProviderUnion&>(
|
||||
Core::System::GetInstance().GetContentProvider());
|
||||
const auto installed_games = cache.ListEntriesFilterOrigin(
|
||||
std::nullopt, FileSys::TitleType::Application, FileSys::ContentRecordType::Program);
|
||||
void GameListWorker::AddInstalledTitlesToGameList() {
|
||||
const auto cache = Service::FileSystem::GetUnionContents();
|
||||
const auto installed_games = cache.ListEntriesFilter(FileSys::TitleType::Application,
|
||||
FileSys::ContentRecordType::Program);
|
||||
|
||||
for (const auto& [slot, game] : installed_games) {
|
||||
if (slot == FileSys::ContentProviderUnionSlot::FrontendManual)
|
||||
continue;
|
||||
|
||||
const auto file = cache.GetEntryUnparsed(game.title_id, game.type);
|
||||
for (const auto& game : installed_games) {
|
||||
const auto file = cache.GetEntryUnparsed(game);
|
||||
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
|
||||
if (!loader)
|
||||
continue;
|
||||
@@ -158,13 +150,45 @@ void GameListWorker::AddTitlesToGameList() {
|
||||
emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id,
|
||||
compatibility_list, patch));
|
||||
}
|
||||
|
||||
const auto control_data = cache.ListEntriesFilter(FileSys::TitleType::Application,
|
||||
FileSys::ContentRecordType::Control);
|
||||
|
||||
for (const auto& entry : control_data) {
|
||||
auto nca = cache.GetEntry(entry);
|
||||
if (nca != nullptr) {
|
||||
nca_control_map.insert_or_assign(entry.title_id, std::move(nca));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path,
|
||||
unsigned int recursion) {
|
||||
const auto callback = [this, target, recursion](u64* num_entries_out,
|
||||
const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
void GameListWorker::FillControlMap(const std::string& dir_path) {
|
||||
const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
if (stop_processing) {
|
||||
// Breaks the callback loop
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string physical_name = directory + DIR_SEP + virtual_name;
|
||||
const QFileInfo file_info(QString::fromStdString(physical_name));
|
||||
if (!file_info.isDir() && file_info.suffix() == QStringLiteral("nca")) {
|
||||
auto nca =
|
||||
std::make_unique<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
|
||||
if (nca->GetType() == FileSys::NCAContentType::Control) {
|
||||
const u64 title_id = nca->GetTitleId();
|
||||
nca_control_map.insert_or_assign(title_id, std::move(nca));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
|
||||
}
|
||||
|
||||
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
|
||||
const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
if (stop_processing) {
|
||||
// Breaks the callback loop.
|
||||
return false;
|
||||
@@ -174,8 +198,7 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
|
||||
const bool is_dir = FileUtil::IsDirectory(physical_name);
|
||||
if (!is_dir &&
|
||||
(HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
|
||||
const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read);
|
||||
auto loader = Loader::GetLoader(file);
|
||||
auto loader = Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read));
|
||||
if (!loader) {
|
||||
return true;
|
||||
}
|
||||
@@ -186,42 +209,31 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<u8> icon;
|
||||
const auto res1 = loader->ReadIcon(icon);
|
||||
|
||||
u64 program_id = 0;
|
||||
const auto res2 = loader->ReadProgramId(program_id);
|
||||
|
||||
if (target == ScanTarget::FillManualContentProvider) {
|
||||
if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
|
||||
provider->AddEntry(FileSys::TitleType::Application,
|
||||
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
|
||||
program_id, file);
|
||||
} else if (res2 == Loader::ResultStatus::Success &&
|
||||
(file_type == Loader::FileType::XCI ||
|
||||
file_type == Loader::FileType::NSP)) {
|
||||
const auto nsp = file_type == Loader::FileType::NSP
|
||||
? std::make_shared<FileSys::NSP>(file)
|
||||
: FileSys::XCI{file}.GetSecurePartitionNSP();
|
||||
for (const auto& title : nsp->GetNCAs()) {
|
||||
for (const auto& entry : title.second) {
|
||||
provider->AddEntry(entry.first.first, entry.first.second, title.first,
|
||||
entry.second->GetBaseFile());
|
||||
}
|
||||
}
|
||||
std::string name = " ";
|
||||
const auto res3 = loader->ReadTitle(name);
|
||||
|
||||
const FileSys::PatchManager patch{program_id};
|
||||
|
||||
if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success &&
|
||||
res2 == Loader::ResultStatus::Success) {
|
||||
// Use from metadata pool.
|
||||
if (nca_control_map.find(program_id) != nca_control_map.end()) {
|
||||
const auto& nca = nca_control_map[program_id];
|
||||
GetMetadataFromControlNCA(patch, *nca, icon, name);
|
||||
}
|
||||
} else {
|
||||
std::vector<u8> icon;
|
||||
const auto res1 = loader->ReadIcon(icon);
|
||||
|
||||
std::string name = " ";
|
||||
const auto res3 = loader->ReadTitle(name);
|
||||
|
||||
const FileSys::PatchManager patch{program_id};
|
||||
|
||||
emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id,
|
||||
compatibility_list, patch));
|
||||
}
|
||||
|
||||
emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id,
|
||||
compatibility_list, patch));
|
||||
} else if (is_dir && recursion > 0) {
|
||||
watch_list.append(QString::fromStdString(physical_name));
|
||||
ScanFileSystem(target, physical_name, recursion - 1);
|
||||
AddFstEntriesToGameList(physical_name, recursion - 1);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -233,11 +245,10 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
|
||||
void GameListWorker::run() {
|
||||
stop_processing = false;
|
||||
watch_list.append(dir_path);
|
||||
provider->ClearAllEntries();
|
||||
ScanFileSystem(ScanTarget::FillManualContentProvider, dir_path.toStdString(),
|
||||
deep_scan ? 256 : 0);
|
||||
AddTitlesToGameList();
|
||||
ScanFileSystem(ScanTarget::PopulateGameList, dir_path.toStdString(), deep_scan ? 256 : 0);
|
||||
FillControlMap(dir_path.toStdString());
|
||||
AddInstalledTitlesToGameList();
|
||||
AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
|
||||
nca_control_map.clear();
|
||||
emit Finished(watch_list);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,8 +33,7 @@ class GameListWorker : public QObject, public QRunnable {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs,
|
||||
FileSys::ManualContentProvider* provider, QString dir_path, bool deep_scan,
|
||||
GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan,
|
||||
const CompatibilityList& compatibility_list);
|
||||
~GameListWorker() override;
|
||||
|
||||
@@ -59,17 +58,12 @@ signals:
|
||||
void Finished(QStringList watch_list);
|
||||
|
||||
private:
|
||||
void AddTitlesToGameList();
|
||||
|
||||
enum class ScanTarget {
|
||||
FillManualContentProvider,
|
||||
PopulateGameList,
|
||||
};
|
||||
|
||||
void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion = 0);
|
||||
void AddInstalledTitlesToGameList();
|
||||
void FillControlMap(const std::string& dir_path);
|
||||
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
|
||||
|
||||
std::shared_ptr<FileSys::VfsFilesystem> vfs;
|
||||
FileSys::ManualContentProvider* provider;
|
||||
std::map<u64, std::unique_ptr<FileSys::NCA>> nca_control_map;
|
||||
QStringList watch_list;
|
||||
QString dir_path;
|
||||
bool deep_scan;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user