Compare commits
92 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b6adb5308 | ||
|
|
872a7bee72 | ||
|
|
e8ff8a66b0 | ||
|
|
723ad4512f | ||
|
|
c1409602da | ||
|
|
9d11303a36 | ||
|
|
adf07cbe17 | ||
|
|
8a320a6ee2 | ||
|
|
1342c53e27 | ||
|
|
ab00552118 | ||
|
|
ec74a4fd4a | ||
|
|
746167f11a | ||
|
|
8dc2f01eae | ||
|
|
e05136f70b | ||
|
|
9f092554c2 | ||
|
|
26b809549b | ||
|
|
c07ebeac19 | ||
|
|
ecd3afdc8e | ||
|
|
30f228a8c9 | ||
|
|
c57d8eb66c | ||
|
|
51f609fee7 | ||
|
|
57ca1e3e69 | ||
|
|
114060fd87 | ||
|
|
d74aa13bd3 | ||
|
|
834d3fe336 | ||
|
|
41566c615b | ||
|
|
9ad3b01d30 | ||
|
|
fd0533ef4c | ||
|
|
ed7a1e1443 | ||
|
|
93da8e0abf | ||
|
|
032e4c4ca3 | ||
|
|
2392e146b0 | ||
|
|
bf41132aa9 | ||
|
|
059465d496 | ||
|
|
64444ff481 | ||
|
|
f71c598907 | ||
|
|
6abc56672c | ||
|
|
99f982dce2 | ||
|
|
29c242721a | ||
|
|
bdf2da4ee8 | ||
|
|
10118c71e0 | ||
|
|
574e89d924 | ||
|
|
47b622825c | ||
|
|
51d7f6bffc | ||
|
|
c892cf01fa | ||
|
|
db47d7e471 | ||
|
|
e0d1f11968 | ||
|
|
39483b92b7 | ||
|
|
0b78cfcc53 | ||
|
|
06ac6460d3 | ||
|
|
e5b004e903 | ||
|
|
59f16f2e02 | ||
|
|
2eaf6c41a4 | ||
|
|
84d3cdf7d7 | ||
|
|
6788ebffc8 | ||
|
|
2d9546848e | ||
|
|
8bd17aa044 | ||
|
|
4e6c667586 | ||
|
|
ffe2e50458 | ||
|
|
6ed6129b4f | ||
|
|
9030a8259f | ||
|
|
fdf76a25ab | ||
|
|
e7850a7f11 | ||
|
|
c1ea6a39a0 | ||
|
|
0a923b4ab3 | ||
|
|
e8a21f5276 | ||
|
|
71c4e876ef | ||
|
|
a63295a872 | ||
|
|
a3734d7e31 | ||
|
|
aa59d77c3b | ||
|
|
8ebeb9ade2 | ||
|
|
3350c0a779 | ||
|
|
1070c020db | ||
|
|
aa44eb639b | ||
|
|
7ad3d4e49c | ||
|
|
a6c048920e | ||
|
|
0c28ab92e6 | ||
|
|
3f602dde0f | ||
|
|
f7ec0bcfc2 | ||
|
|
d870cc5ad7 | ||
|
|
39e895c5ff | ||
|
|
71817afbe9 | ||
|
|
8b98f60e3c | ||
|
|
09ac66388c | ||
|
|
c8554d218b | ||
|
|
92ea1c32d6 | ||
|
|
254b1e3df7 | ||
|
|
1b2872eebc | ||
|
|
a8d4927e29 | ||
|
|
ecccfe0337 | ||
|
|
3ea48e8ebe | ||
|
|
5b7ec71fb7 |
@@ -163,12 +163,6 @@ else()
|
||||
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
|
||||
endif()
|
||||
|
||||
# Fix GCC C++17 and Boost.ICL incompatibility (needed to build dynarmic)
|
||||
# See https://bugzilla.redhat.com/show_bug.cgi?id=1485641#c1
|
||||
if (CMAKE_COMPILER_IS_GNUCC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-new-ttp-matching")
|
||||
endif()
|
||||
|
||||
# Set file offset size to 64 bits.
|
||||
#
|
||||
# On modern Unixes, this is typically already the case. The lone exception is
|
||||
@@ -185,9 +179,9 @@ set_property(DIRECTORY APPEND PROPERTY
|
||||
# System imported libraries
|
||||
# ======================
|
||||
|
||||
find_package(Boost 1.63.0 QUIET)
|
||||
find_package(Boost 1.66.0 QUIET)
|
||||
if (NOT Boost_FOUND)
|
||||
message(STATUS "Boost 1.63.0 or newer not found, falling back to externals")
|
||||
message(STATUS "Boost 1.66.0 or newer not found, falling back to externals")
|
||||
|
||||
set(BOOST_ROOT "${PROJECT_SOURCE_DIR}/externals/boost")
|
||||
set(Boost_NO_SYSTEM_PATHS OFF)
|
||||
|
||||
2
externals/opus
vendored
2
externals/opus
vendored
Submodule externals/opus updated: b2871922a1...562f8ba555
@@ -92,10 +92,14 @@ add_library(common STATIC
|
||||
logging/text_formatter.cpp
|
||||
logging/text_formatter.h
|
||||
math_util.h
|
||||
memory_hook.cpp
|
||||
memory_hook.h
|
||||
microprofile.cpp
|
||||
microprofile.h
|
||||
microprofileui.h
|
||||
misc.cpp
|
||||
page_table.cpp
|
||||
page_table.h
|
||||
param_package.cpp
|
||||
param_package.h
|
||||
quaternion.h
|
||||
@@ -114,6 +118,8 @@ add_library(common STATIC
|
||||
threadsafe_queue.h
|
||||
timer.cpp
|
||||
timer.h
|
||||
uint128.cpp
|
||||
uint128.h
|
||||
vector_math.h
|
||||
web_result.h
|
||||
)
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/memory_hook.h"
|
||||
#include "common/memory_hook.h"
|
||||
|
||||
namespace Memory {
|
||||
namespace Common {
|
||||
|
||||
MemoryHook::~MemoryHook() = default;
|
||||
|
||||
} // namespace Memory
|
||||
} // namespace Common
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Memory {
|
||||
namespace Common {
|
||||
|
||||
/**
|
||||
* Memory hooks have two purposes:
|
||||
@@ -44,4 +44,4 @@ public:
|
||||
};
|
||||
|
||||
using MemoryHookPointer = std::shared_ptr<MemoryHook>;
|
||||
} // namespace Memory
|
||||
} // namespace Common
|
||||
29
src/common/page_table.cpp
Normal file
29
src/common/page_table.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/page_table.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
PageTable::PageTable(std::size_t page_size_in_bits) : page_size_in_bits{page_size_in_bits} {}
|
||||
|
||||
PageTable::~PageTable() = default;
|
||||
|
||||
void PageTable::Resize(std::size_t address_space_width_in_bits) {
|
||||
const std::size_t num_page_table_entries = 1ULL
|
||||
<< (address_space_width_in_bits - page_size_in_bits);
|
||||
|
||||
pointers.resize(num_page_table_entries);
|
||||
attributes.resize(num_page_table_entries);
|
||||
|
||||
// The default is a 39-bit address space, which causes an initial 1GB allocation size. If the
|
||||
// vector size is subsequently decreased (via resize), the vector might not automatically
|
||||
// actually reallocate/resize its underlying allocation, which wastes up to ~800 MB for
|
||||
// 36-bit titles. Call shrink_to_fit to reduce capacity to what's actually in use.
|
||||
|
||||
pointers.shrink_to_fit();
|
||||
attributes.shrink_to_fit();
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
80
src/common/page_table.h
Normal file
80
src/common/page_table.h
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <boost/icl/interval_map.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "common/memory_hook.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
enum class PageType : u8 {
|
||||
/// Page is unmapped and should cause an access error.
|
||||
Unmapped,
|
||||
/// Page is mapped to regular memory. This is the only type you can get pointers to.
|
||||
Memory,
|
||||
/// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
|
||||
/// invalidation
|
||||
RasterizerCachedMemory,
|
||||
/// Page is mapped to a I/O region. Writing and reading to this page is handled by functions.
|
||||
Special,
|
||||
};
|
||||
|
||||
struct SpecialRegion {
|
||||
enum class Type {
|
||||
DebugHook,
|
||||
IODevice,
|
||||
} type;
|
||||
|
||||
MemoryHookPointer handler;
|
||||
|
||||
bool operator<(const SpecialRegion& other) const {
|
||||
return std::tie(type, handler) < std::tie(other.type, other.handler);
|
||||
}
|
||||
|
||||
bool operator==(const SpecialRegion& other) const {
|
||||
return std::tie(type, handler) == std::tie(other.type, other.handler);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A (reasonably) fast way of allowing switchable and remappable process address spaces. It loosely
|
||||
* mimics the way a real CPU page table works.
|
||||
*/
|
||||
struct PageTable {
|
||||
explicit PageTable(std::size_t page_size_in_bits);
|
||||
~PageTable();
|
||||
|
||||
/**
|
||||
* Resizes the page table to be able to accomodate enough pages within
|
||||
* a given address space.
|
||||
*
|
||||
* @param address_space_width_in_bits The address size width in bits.
|
||||
*/
|
||||
void Resize(std::size_t address_space_width_in_bits);
|
||||
|
||||
/**
|
||||
* Vector of memory pointers backing each page. An entry can only be non-null if the
|
||||
* corresponding entry in the `attributes` vector is of type `Memory`.
|
||||
*/
|
||||
std::vector<u8*> pointers;
|
||||
|
||||
/**
|
||||
* Contains MMIO handlers that back memory regions whose entries in the `attribute` vector is
|
||||
* of type `Special`.
|
||||
*/
|
||||
boost::icl::interval_map<VAddr, std::set<SpecialRegion>> special_regions;
|
||||
|
||||
/**
|
||||
* Vector of fine grained page attributes. If it is set to any value other than `Memory`, then
|
||||
* the corresponding entry in `pointers` MUST be set to null.
|
||||
*/
|
||||
std::vector<PageType> attributes;
|
||||
|
||||
const std::size_t page_size_in_bits{};
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <boost/range/algorithm_ext/erase.hpp>
|
||||
|
||||
namespace Common {
|
||||
|
||||
@@ -111,8 +110,9 @@ struct ThreadQueueList {
|
||||
}
|
||||
|
||||
void remove(Priority priority, const T& thread_id) {
|
||||
Queue* cur = &queues[priority];
|
||||
boost::remove_erase(cur->data, thread_id);
|
||||
Queue* const cur = &queues[priority];
|
||||
const auto iter = std::remove(cur->data.begin(), cur->data.end(), thread_id);
|
||||
cur->data.erase(iter, cur->data.end());
|
||||
}
|
||||
|
||||
void rotate(Priority priority) {
|
||||
|
||||
41
src/common/uint128.cpp
Normal file
41
src/common/uint128.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
|
||||
#pragma intrinsic(_umul128)
|
||||
#endif
|
||||
#include <cstring>
|
||||
#include "common/uint128.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
u128 Multiply64Into128(u64 a, u64 b) {
|
||||
u128 result;
|
||||
#ifdef _MSC_VER
|
||||
result[0] = _umul128(a, b, &result[1]);
|
||||
#else
|
||||
unsigned __int128 tmp = a;
|
||||
tmp *= b;
|
||||
std::memcpy(&result, &tmp, sizeof(u128));
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
std::pair<u64, u64> Divide128On32(u128 dividend, u32 divisor) {
|
||||
u64 remainder = dividend[0] % divisor;
|
||||
u64 accum = dividend[0] / divisor;
|
||||
if (dividend[1] == 0)
|
||||
return {accum, remainder};
|
||||
// We ignore dividend[1] / divisor as that overflows
|
||||
const u64 first_segment = (dividend[1] % divisor) << 32;
|
||||
accum += (first_segment / divisor) << 32;
|
||||
const u64 second_segment = (first_segment % divisor) << 32;
|
||||
accum += (second_segment / divisor);
|
||||
remainder += second_segment % divisor;
|
||||
if (remainder >= divisor) {
|
||||
accum++;
|
||||
remainder -= divisor;
|
||||
}
|
||||
return {accum, remainder};
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
14
src/common/uint128.h
Normal file
14
src/common/uint128.h
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
#include <utility>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
// This function multiplies 2 u64 values and produces a u128 value;
|
||||
u128 Multiply64Into128(u64 a, u64 b);
|
||||
|
||||
// This function divides a u128 by a u32 value and produces two u64 values:
|
||||
// the result of division and the remainder
|
||||
std::pair<u64, u64> Divide128On32(u128 dividend, u32 divisor);
|
||||
|
||||
} // namespace Common
|
||||
@@ -419,8 +419,6 @@ add_library(core STATIC
|
||||
loader/deconstructed_rom_directory.h
|
||||
loader/elf.cpp
|
||||
loader/elf.h
|
||||
loader/linker.cpp
|
||||
loader/linker.h
|
||||
loader/loader.cpp
|
||||
loader/loader.h
|
||||
loader/nax.cpp
|
||||
@@ -437,8 +435,6 @@ add_library(core STATIC
|
||||
loader/xci.h
|
||||
memory.cpp
|
||||
memory.h
|
||||
memory_hook.cpp
|
||||
memory_hook.h
|
||||
memory_setup.h
|
||||
perf_stats.cpp
|
||||
perf_stats.h
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "core/core.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
@@ -119,7 +120,7 @@ public:
|
||||
return std::max(parent.core_timing.GetDowncount(), 0);
|
||||
}
|
||||
u64 GetCNTPCT() override {
|
||||
return parent.core_timing.GetTicks();
|
||||
return Timing::CpuCyclesToClockCycles(parent.core_timing.GetTicks());
|
||||
}
|
||||
|
||||
ARM_Dynarmic& parent;
|
||||
@@ -151,7 +152,7 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
|
||||
config.tpidr_el0 = &cb->tpidr_el0;
|
||||
config.dczid_el0 = 4;
|
||||
config.ctr_el0 = 0x8444c004;
|
||||
config.cntfrq_el0 = 19200000; // Value from fusee.
|
||||
config.cntfrq_el0 = Timing::CNTFREQ;
|
||||
|
||||
// Unpredictable instructions
|
||||
config.define_unpredictable_behaviour = true;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/arm/unicorn/arm_unicorn.h"
|
||||
|
||||
namespace Memory {
|
||||
namespace Common {
|
||||
struct PageTable;
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ private:
|
||||
Timing::CoreTiming& core_timing;
|
||||
DynarmicExclusiveMonitor& exclusive_monitor;
|
||||
|
||||
Memory::PageTable* current_page_table = nullptr;
|
||||
Common::PageTable* current_page_table = nullptr;
|
||||
};
|
||||
|
||||
class DynarmicExclusiveMonitor final : public ExclusiveMonitor {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <cinttypes>
|
||||
#include <limits>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/uint128.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
|
||||
@@ -60,4 +61,9 @@ s64 nsToCycles(u64 ns) {
|
||||
return (BASE_CLOCK_RATE * static_cast<s64>(ns)) / 1000000000;
|
||||
}
|
||||
|
||||
u64 CpuCyclesToClockCycles(u64 ticks) {
|
||||
const u128 temporal = Common::Multiply64Into128(ticks, CNTFREQ);
|
||||
return Common::Divide128On32(temporal, static_cast<u32>(BASE_CLOCK_RATE)).first;
|
||||
}
|
||||
|
||||
} // namespace Core::Timing
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Core::Timing {
|
||||
// The below clock rate is based on Switch's clockspeed being widely known as 1.020GHz
|
||||
// The exact value used is of course unverified.
|
||||
constexpr u64 BASE_CLOCK_RATE = 1019215872; // Switch clock speed is 1020MHz un/docked
|
||||
constexpr u64 CNTFREQ = 19200000; // Value from fusee.
|
||||
|
||||
inline s64 msToCycles(int ms) {
|
||||
// since ms is int there is no way to overflow
|
||||
@@ -61,4 +62,6 @@ inline u64 cyclesToMs(s64 cycles) {
|
||||
return cycles * 1000 / BASE_CLOCK_RATE;
|
||||
}
|
||||
|
||||
u64 CpuCyclesToClockCycles(u64 ticks);
|
||||
|
||||
} // namespace Core::Timing
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/server_session.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace IPC {
|
||||
|
||||
constexpr ResultCode ERR_REMOTE_PROCESS_DEAD{ErrorModule::HIPC, 301};
|
||||
|
||||
class RequestHelperBase {
|
||||
protected:
|
||||
Kernel::HLERequestContext* context = nullptr;
|
||||
@@ -271,6 +274,20 @@ inline void ResponseBuilder::Push(u64 value) {
|
||||
Push(static_cast<u32>(value >> 32));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void ResponseBuilder::Push(float value) {
|
||||
u32 integral;
|
||||
std::memcpy(&integral, &value, sizeof(u32));
|
||||
Push(integral);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void ResponseBuilder::Push(double value) {
|
||||
u64 integral;
|
||||
std::memcpy(&integral, &value, sizeof(u64));
|
||||
Push(integral);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void ResponseBuilder::Push(bool value) {
|
||||
Push(static_cast<u8>(value));
|
||||
@@ -362,6 +379,11 @@ inline u32 RequestParser::Pop() {
|
||||
return cmdbuf[index++];
|
||||
}
|
||||
|
||||
template <>
|
||||
inline s32 RequestParser::Pop() {
|
||||
return static_cast<s32>(Pop<u32>());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void RequestParser::PopRaw(T& value) {
|
||||
std::memcpy(&value, cmdbuf + index, sizeof(T));
|
||||
@@ -392,11 +414,37 @@ inline u64 RequestParser::Pop() {
|
||||
return msw << 32 | lsw;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline s8 RequestParser::Pop() {
|
||||
return static_cast<s8>(Pop<u8>());
|
||||
}
|
||||
|
||||
template <>
|
||||
inline s16 RequestParser::Pop() {
|
||||
return static_cast<s16>(Pop<u16>());
|
||||
}
|
||||
|
||||
template <>
|
||||
inline s64 RequestParser::Pop() {
|
||||
return static_cast<s64>(Pop<u64>());
|
||||
}
|
||||
|
||||
template <>
|
||||
inline float RequestParser::Pop() {
|
||||
const u32 value = Pop<u32>();
|
||||
float real;
|
||||
std::memcpy(&real, &value, sizeof(real));
|
||||
return real;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline double RequestParser::Pop() {
|
||||
const u64 value = Pop<u64>();
|
||||
float real;
|
||||
std::memcpy(&real, &value, sizeof(real));
|
||||
return real;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool RequestParser::Pop() {
|
||||
return Pop<u8>() != 0;
|
||||
|
||||
@@ -33,10 +33,11 @@ ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() {
|
||||
// Create a new session pair, let the created sessions inherit the parent port's HLE handler.
|
||||
auto sessions = ServerSession::CreateSessionPair(kernel, server_port->GetName(), this);
|
||||
|
||||
if (server_port->hle_handler)
|
||||
server_port->hle_handler->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
|
||||
else
|
||||
server_port->pending_sessions.push_back(std::get<SharedPtr<ServerSession>>(sessions));
|
||||
if (server_port->HasHLEHandler()) {
|
||||
server_port->GetHLEHandler()->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
|
||||
} else {
|
||||
server_port->AppendPendingSession(std::get<SharedPtr<ServerSession>>(sessions));
|
||||
}
|
||||
|
||||
// Wake the threads waiting on the ServerPort
|
||||
server_port->WakeupAllWaitingThreads();
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace {
|
||||
*/
|
||||
void SetupMainThread(Process& owner_process, KernelCore& kernel, VAddr entry_point, u32 priority) {
|
||||
// Setup page table so we can write to memory
|
||||
SetCurrentPageTable(&owner_process.VMManager().page_table);
|
||||
Memory::SetCurrentPageTable(&owner_process.VMManager().page_table);
|
||||
|
||||
// Initialize new "main" thread
|
||||
const VAddr stack_top = owner_process.VMManager().GetTLSIORegionEndAddress();
|
||||
|
||||
@@ -96,7 +96,7 @@ void Scheduler::SwitchContext(Thread* new_thread) {
|
||||
auto* const thread_owner_process = current_thread->GetOwnerProcess();
|
||||
if (previous_process != thread_owner_process) {
|
||||
system.Kernel().MakeCurrentProcess(thread_owner_process);
|
||||
SetCurrentPageTable(&thread_owner_process->VMManager().page_table);
|
||||
Memory::SetCurrentPageTable(&thread_owner_process->VMManager().page_table);
|
||||
}
|
||||
|
||||
cpu_core.LoadContext(new_thread->GetContext());
|
||||
@@ -199,8 +199,7 @@ void Scheduler::YieldWithoutLoadBalancing(Thread* thread) {
|
||||
ASSERT(thread->GetPriority() < THREADPRIO_COUNT);
|
||||
|
||||
// Yield this thread -- sleep for zero time and force reschedule to different thread
|
||||
WaitCurrentThread_Sleep();
|
||||
GetCurrentThread()->WakeAfterDelay(0);
|
||||
GetCurrentThread()->Sleep(0);
|
||||
}
|
||||
|
||||
void Scheduler::YieldWithLoadBalancing(Thread* thread) {
|
||||
@@ -215,8 +214,7 @@ void Scheduler::YieldWithLoadBalancing(Thread* thread) {
|
||||
ASSERT(priority < THREADPRIO_COUNT);
|
||||
|
||||
// Sleep for zero time to be able to force reschedule to different thread
|
||||
WaitCurrentThread_Sleep();
|
||||
GetCurrentThread()->WakeAfterDelay(0);
|
||||
GetCurrentThread()->Sleep(0);
|
||||
|
||||
Thread* suggested_thread = nullptr;
|
||||
|
||||
|
||||
@@ -26,6 +26,10 @@ ResultVal<SharedPtr<ServerSession>> ServerPort::Accept() {
|
||||
return MakeResult(std::move(session));
|
||||
}
|
||||
|
||||
void ServerPort::AppendPendingSession(SharedPtr<ServerSession> pending_session) {
|
||||
pending_sessions.push_back(std::move(pending_session));
|
||||
}
|
||||
|
||||
bool ServerPort::ShouldWait(Thread* thread) const {
|
||||
// If there are no pending sessions, we wait until a new one is added.
|
||||
return pending_sessions.empty();
|
||||
|
||||
@@ -22,6 +22,8 @@ class SessionRequestHandler;
|
||||
|
||||
class ServerPort final : public WaitObject {
|
||||
public:
|
||||
using HLEHandler = std::shared_ptr<SessionRequestHandler>;
|
||||
|
||||
/**
|
||||
* Creates a pair of ServerPort and an associated ClientPort.
|
||||
*
|
||||
@@ -51,22 +53,27 @@ public:
|
||||
*/
|
||||
ResultVal<SharedPtr<ServerSession>> Accept();
|
||||
|
||||
/// Whether or not this server port has an HLE handler available.
|
||||
bool HasHLEHandler() const {
|
||||
return hle_handler != nullptr;
|
||||
}
|
||||
|
||||
/// Gets the HLE handler for this port.
|
||||
HLEHandler GetHLEHandler() const {
|
||||
return hle_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the HLE handler template for the port. ServerSessions crated by connecting to this port
|
||||
* will inherit a reference to this handler.
|
||||
*/
|
||||
void SetHleHandler(std::shared_ptr<SessionRequestHandler> hle_handler_) {
|
||||
void SetHleHandler(HLEHandler hle_handler_) {
|
||||
hle_handler = std::move(hle_handler_);
|
||||
}
|
||||
|
||||
std::string name; ///< Name of port (optional)
|
||||
|
||||
/// ServerSessions waiting to be accepted by the port
|
||||
std::vector<SharedPtr<ServerSession>> pending_sessions;
|
||||
|
||||
/// This session's HLE request handler template (optional)
|
||||
/// ServerSessions created from this port inherit a reference to this handler.
|
||||
std::shared_ptr<SessionRequestHandler> hle_handler;
|
||||
/// Appends a ServerSession to the collection of ServerSessions
|
||||
/// waiting to be accepted by this port.
|
||||
void AppendPendingSession(SharedPtr<ServerSession> pending_session);
|
||||
|
||||
bool ShouldWait(Thread* thread) const override;
|
||||
void Acquire(Thread* thread) override;
|
||||
@@ -74,6 +81,16 @@ public:
|
||||
private:
|
||||
explicit ServerPort(KernelCore& kernel);
|
||||
~ServerPort() override;
|
||||
|
||||
/// ServerSessions waiting to be accepted by the port
|
||||
std::vector<SharedPtr<ServerSession>> pending_sessions;
|
||||
|
||||
/// This session's HLE request handler template (optional)
|
||||
/// ServerSessions created from this port inherit a reference to this handler.
|
||||
HLEHandler hle_handler;
|
||||
|
||||
/// Name of the port (optional)
|
||||
std::string name;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -1284,10 +1284,14 @@ static ResultCode StartThread(Handle thread_handle) {
|
||||
|
||||
/// Called when a thread exits
|
||||
static void ExitThread() {
|
||||
LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", Core::CurrentArmInterface().GetPC());
|
||||
auto& system = Core::System::GetInstance();
|
||||
|
||||
ExitCurrentThread();
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC());
|
||||
|
||||
auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
|
||||
current_thread->Stop();
|
||||
system.CurrentScheduler().RemoveThread(current_thread);
|
||||
system.PrepareReschedule();
|
||||
}
|
||||
|
||||
/// Sleep the current thread
|
||||
@@ -1300,32 +1304,32 @@ static void SleepThread(s64 nanoseconds) {
|
||||
YieldAndWaitForLoadBalancing = -2,
|
||||
};
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& scheduler = system.CurrentScheduler();
|
||||
auto* const current_thread = scheduler.GetCurrentThread();
|
||||
|
||||
if (nanoseconds <= 0) {
|
||||
auto& scheduler{Core::System::GetInstance().CurrentScheduler()};
|
||||
switch (static_cast<SleepType>(nanoseconds)) {
|
||||
case SleepType::YieldWithoutLoadBalancing:
|
||||
scheduler.YieldWithoutLoadBalancing(GetCurrentThread());
|
||||
scheduler.YieldWithoutLoadBalancing(current_thread);
|
||||
break;
|
||||
case SleepType::YieldWithLoadBalancing:
|
||||
scheduler.YieldWithLoadBalancing(GetCurrentThread());
|
||||
scheduler.YieldWithLoadBalancing(current_thread);
|
||||
break;
|
||||
case SleepType::YieldAndWaitForLoadBalancing:
|
||||
scheduler.YieldAndWaitForLoadBalancing(GetCurrentThread());
|
||||
scheduler.YieldAndWaitForLoadBalancing(current_thread);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds);
|
||||
}
|
||||
} else {
|
||||
// Sleep current thread and check for next thread to schedule
|
||||
WaitCurrentThread_Sleep();
|
||||
|
||||
// Create an event to wake the thread up after the specified nanosecond delay has passed
|
||||
GetCurrentThread()->WakeAfterDelay(nanoseconds);
|
||||
current_thread->Sleep(nanoseconds);
|
||||
}
|
||||
|
||||
// Reschedule all CPU cores
|
||||
for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i)
|
||||
Core::System::GetInstance().CpuCore(i).PrepareReschedule();
|
||||
for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i) {
|
||||
system.CpuCore(i).PrepareReschedule();
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait process wide key atomic
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/range/algorithm_ext/erase.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -68,17 +66,6 @@ void Thread::Stop() {
|
||||
owner_process->FreeTLSSlot(tls_address);
|
||||
}
|
||||
|
||||
void WaitCurrentThread_Sleep() {
|
||||
Thread* thread = GetCurrentThread();
|
||||
thread->SetStatus(ThreadStatus::WaitSleep);
|
||||
}
|
||||
|
||||
void ExitCurrentThread() {
|
||||
Thread* thread = GetCurrentThread();
|
||||
thread->Stop();
|
||||
Core::System::GetInstance().CurrentScheduler().RemoveThread(thread);
|
||||
}
|
||||
|
||||
void Thread::WakeAfterDelay(s64 nanoseconds) {
|
||||
// Don't schedule a wakeup if the thread wants to wait forever
|
||||
if (nanoseconds == -1)
|
||||
@@ -269,8 +256,8 @@ void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
|
||||
if (thread->lock_owner == this) {
|
||||
// If the thread is already waiting for this thread to release the mutex, ensure that the
|
||||
// waiters list is consistent and return without doing anything.
|
||||
auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(itr != wait_mutex_threads.end());
|
||||
const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(iter != wait_mutex_threads.end());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -278,11 +265,16 @@ void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
|
||||
ASSERT(thread->lock_owner == nullptr);
|
||||
|
||||
// Ensure that the thread is not already in the list of mutex waiters
|
||||
auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(itr == wait_mutex_threads.end());
|
||||
const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(iter == wait_mutex_threads.end());
|
||||
|
||||
// Keep the list in an ordered fashion
|
||||
const auto insertion_point = std::find_if(
|
||||
wait_mutex_threads.begin(), wait_mutex_threads.end(),
|
||||
[&thread](const auto& entry) { return entry->GetPriority() > thread->GetPriority(); });
|
||||
wait_mutex_threads.insert(insertion_point, thread);
|
||||
thread->lock_owner = this;
|
||||
wait_mutex_threads.emplace_back(std::move(thread));
|
||||
|
||||
UpdatePriority();
|
||||
}
|
||||
|
||||
@@ -290,32 +282,44 @@ void Thread::RemoveMutexWaiter(SharedPtr<Thread> thread) {
|
||||
ASSERT(thread->lock_owner == this);
|
||||
|
||||
// Ensure that the thread is in the list of mutex waiters
|
||||
auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(itr != wait_mutex_threads.end());
|
||||
const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(iter != wait_mutex_threads.end());
|
||||
|
||||
wait_mutex_threads.erase(iter);
|
||||
|
||||
boost::remove_erase(wait_mutex_threads, thread);
|
||||
thread->lock_owner = nullptr;
|
||||
UpdatePriority();
|
||||
}
|
||||
|
||||
void Thread::UpdatePriority() {
|
||||
// Find the highest priority among all the threads that are waiting for this thread's lock
|
||||
// If any of the threads waiting on the mutex have a higher priority
|
||||
// (taking into account priority inheritance), then this thread inherits
|
||||
// that thread's priority.
|
||||
u32 new_priority = nominal_priority;
|
||||
for (const auto& thread : wait_mutex_threads) {
|
||||
if (thread->nominal_priority < new_priority)
|
||||
new_priority = thread->nominal_priority;
|
||||
if (!wait_mutex_threads.empty()) {
|
||||
if (wait_mutex_threads.front()->current_priority < new_priority) {
|
||||
new_priority = wait_mutex_threads.front()->current_priority;
|
||||
}
|
||||
}
|
||||
|
||||
if (new_priority == current_priority)
|
||||
if (new_priority == current_priority) {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler->SetThreadPriority(this, new_priority);
|
||||
|
||||
current_priority = new_priority;
|
||||
|
||||
if (!lock_owner) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure that the thread is within the correct location in the waiting list.
|
||||
auto old_owner = lock_owner;
|
||||
lock_owner->RemoveMutexWaiter(this);
|
||||
old_owner->AddMutexWaiter(this);
|
||||
|
||||
// Recursively update the priority of the thread that depends on the priority of this one.
|
||||
if (lock_owner)
|
||||
lock_owner->UpdatePriority();
|
||||
lock_owner->UpdatePriority();
|
||||
}
|
||||
|
||||
void Thread::ChangeCore(u32 core, u64 mask) {
|
||||
@@ -391,6 +395,14 @@ void Thread::SetActivity(ThreadActivity value) {
|
||||
}
|
||||
}
|
||||
|
||||
void Thread::Sleep(s64 nanoseconds) {
|
||||
// Sleep current thread and check for next thread to schedule
|
||||
SetStatus(ThreadStatus::WaitSleep);
|
||||
|
||||
// Create an event to wake the thread up after the specified nanosecond delay has passed
|
||||
WakeAfterDelay(nanoseconds);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
|
||||
@@ -383,6 +383,9 @@ public:
|
||||
|
||||
void SetActivity(ThreadActivity value);
|
||||
|
||||
/// Sleeps this thread for the given amount of nanoseconds.
|
||||
void Sleep(s64 nanoseconds);
|
||||
|
||||
private:
|
||||
explicit Thread(KernelCore& kernel);
|
||||
~Thread() override;
|
||||
@@ -398,8 +401,14 @@ private:
|
||||
VAddr entry_point = 0;
|
||||
VAddr stack_top = 0;
|
||||
|
||||
u32 nominal_priority = 0; ///< Nominal thread priority, as set by the emulated application
|
||||
u32 current_priority = 0; ///< Current thread priority, can be temporarily changed
|
||||
/// Nominal thread priority, as set by the emulated application.
|
||||
/// The nominal priority is the thread priority without priority
|
||||
/// inheritance taken into account.
|
||||
u32 nominal_priority = 0;
|
||||
|
||||
/// Current thread priority. This may change over the course of the
|
||||
/// thread's lifetime in order to facilitate priority inheritance.
|
||||
u32 current_priority = 0;
|
||||
|
||||
u64 total_cpu_time_ticks = 0; ///< Total CPU running ticks.
|
||||
u64 last_running_ticks = 0; ///< CPU tick when thread was last running
|
||||
@@ -460,14 +469,4 @@ private:
|
||||
*/
|
||||
Thread* GetCurrentThread();
|
||||
|
||||
/**
|
||||
* Waits the current thread on a sleep
|
||||
*/
|
||||
void WaitCurrentThread_Sleep();
|
||||
|
||||
/**
|
||||
* Stops the current thread and removes it from the thread_list
|
||||
*/
|
||||
void ExitCurrentThread();
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
#include <utility>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/memory_hook.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/program_metadata.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory_hook.h"
|
||||
#include "core/memory_setup.h"
|
||||
|
||||
namespace Kernel {
|
||||
@@ -177,7 +177,7 @@ ResultVal<VAddr> VMManager::FindFreeRegion(u64 size) const {
|
||||
|
||||
ResultVal<VMManager::VMAHandle> VMManager::MapMMIO(VAddr target, PAddr paddr, u64 size,
|
||||
MemoryState state,
|
||||
Memory::MemoryHookPointer mmio_handler) {
|
||||
Common::MemoryHookPointer mmio_handler) {
|
||||
// This is the appropriately sized VMA that will turn into our allocation.
|
||||
CASCADE_RESULT(VMAIter vma_handle, CarveVMA(target, size));
|
||||
VirtualMemoryArea& final_vma = vma_handle->second;
|
||||
@@ -624,7 +624,7 @@ void VMManager::ClearPageTable() {
|
||||
std::fill(page_table.pointers.begin(), page_table.pointers.end(), nullptr);
|
||||
page_table.special_regions.clear();
|
||||
std::fill(page_table.attributes.begin(), page_table.attributes.end(),
|
||||
Memory::PageType::Unmapped);
|
||||
Common::PageType::Unmapped);
|
||||
}
|
||||
|
||||
VMManager::CheckResults VMManager::CheckRangeState(VAddr address, u64 size, MemoryState state_mask,
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/memory_hook.h"
|
||||
#include "common/page_table.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory_hook.h"
|
||||
|
||||
namespace FileSys {
|
||||
enum class ProgramAddressSpaceType : u8;
|
||||
@@ -290,7 +291,7 @@ struct VirtualMemoryArea {
|
||||
// Settings for type = MMIO
|
||||
/// Physical address of the register area this VMA maps to.
|
||||
PAddr paddr = 0;
|
||||
Memory::MemoryHookPointer mmio_handler = nullptr;
|
||||
Common::MemoryHookPointer mmio_handler = nullptr;
|
||||
|
||||
/// Tests if this area can be merged to the right with `next`.
|
||||
bool CanBeMergedWith(const VirtualMemoryArea& next) const;
|
||||
@@ -368,7 +369,7 @@ public:
|
||||
* @param mmio_handler The handler that will implement read and write for this MMIO region.
|
||||
*/
|
||||
ResultVal<VMAHandle> MapMMIO(VAddr target, PAddr paddr, u64 size, MemoryState state,
|
||||
Memory::MemoryHookPointer mmio_handler);
|
||||
Common::MemoryHookPointer mmio_handler);
|
||||
|
||||
/// Unmaps a range of addresses, splitting VMAs as necessary.
|
||||
ResultCode UnmapRange(VAddr target, u64 size);
|
||||
@@ -509,7 +510,7 @@ public:
|
||||
|
||||
/// Each VMManager has its own page table, which is set as the main one when the owning process
|
||||
/// is scheduled.
|
||||
Memory::PageTable page_table;
|
||||
Common::PageTable page_table{Memory::PAGE_BITS};
|
||||
|
||||
private:
|
||||
using VMAIter = VMAMap::iterator;
|
||||
|
||||
@@ -12,14 +12,6 @@
|
||||
|
||||
// All the constants in this file come from http://switchbrew.org/index.php?title=Error_codes
|
||||
|
||||
/**
|
||||
* Detailed description of the error. Code 0 always means success.
|
||||
*/
|
||||
enum class ErrorDescription : u32 {
|
||||
Success = 0,
|
||||
RemoteProcessDead = 301,
|
||||
};
|
||||
|
||||
/**
|
||||
* Identifies the module which caused the error. Error codes can be propagated through a call
|
||||
* chain, meaning that this doesn't always correspond to the module where the API call made is
|
||||
@@ -120,7 +112,7 @@ enum class ErrorModule : u32 {
|
||||
ShopN = 811,
|
||||
};
|
||||
|
||||
/// Encapsulates a CTR-OS error code, allowing it to be separated into its constituent fields.
|
||||
/// Encapsulates a Horizon OS error code, allowing it to be separated into its constituent fields.
|
||||
union ResultCode {
|
||||
u32 raw;
|
||||
|
||||
@@ -133,17 +125,9 @@ union ResultCode {
|
||||
|
||||
constexpr explicit ResultCode(u32 raw) : raw(raw) {}
|
||||
|
||||
constexpr ResultCode(ErrorModule module, ErrorDescription description)
|
||||
: ResultCode(module, static_cast<u32>(description)) {}
|
||||
|
||||
constexpr ResultCode(ErrorModule module_, u32 description_)
|
||||
: raw(module.FormatValue(module_) | description.FormatValue(description_)) {}
|
||||
|
||||
constexpr ResultCode& operator=(const ResultCode& o) {
|
||||
raw = o.raw;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr bool IsSuccess() const {
|
||||
return raw == 0;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
#include <stack>
|
||||
#include "audio_core/audio_renderer.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
@@ -93,38 +93,84 @@ void IWindowController::AcquireForegroundRights(Kernel::HLERequestContext& ctx)
|
||||
}
|
||||
|
||||
IAudioController::IAudioController() : ServiceFramework("IAudioController") {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IAudioController::SetExpectedMasterVolume, "SetExpectedMasterVolume"},
|
||||
{1, &IAudioController::GetMainAppletExpectedMasterVolume,
|
||||
"GetMainAppletExpectedMasterVolume"},
|
||||
{2, &IAudioController::GetLibraryAppletExpectedMasterVolume,
|
||||
"GetLibraryAppletExpectedMasterVolume"},
|
||||
{3, nullptr, "ChangeMainAppletMasterVolume"},
|
||||
{4, nullptr, "SetTransparentVolumeRate"},
|
||||
{1, &IAudioController::GetMainAppletExpectedMasterVolume, "GetMainAppletExpectedMasterVolume"},
|
||||
{2, &IAudioController::GetLibraryAppletExpectedMasterVolume, "GetLibraryAppletExpectedMasterVolume"},
|
||||
{3, &IAudioController::ChangeMainAppletMasterVolume, "ChangeMainAppletMasterVolume"},
|
||||
{4, &IAudioController::SetTransparentAudioRate, "SetTransparentVolumeRate"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IAudioController::~IAudioController() = default;
|
||||
|
||||
void IAudioController::SetExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
const float main_applet_volume_tmp = rp.Pop<float>();
|
||||
const float library_applet_volume_tmp = rp.Pop<float>();
|
||||
|
||||
LOG_DEBUG(Service_AM, "called. main_applet_volume={}, library_applet_volume={}",
|
||||
main_applet_volume_tmp, library_applet_volume_tmp);
|
||||
|
||||
// Ensure the volume values remain within the 0-100% range
|
||||
main_applet_volume = std::clamp(main_applet_volume_tmp, min_allowed_volume, max_allowed_volume);
|
||||
library_applet_volume =
|
||||
std::clamp(library_applet_volume_tmp, min_allowed_volume, max_allowed_volume);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void IAudioController::GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_AM, "called. main_applet_volume={}", main_applet_volume);
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(volume);
|
||||
rb.Push(main_applet_volume);
|
||||
}
|
||||
|
||||
void IAudioController::GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_AM, "called. library_applet_volume={}", library_applet_volume);
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(volume);
|
||||
rb.Push(library_applet_volume);
|
||||
}
|
||||
|
||||
void IAudioController::ChangeMainAppletMasterVolume(Kernel::HLERequestContext& ctx) {
|
||||
struct Parameters {
|
||||
float volume;
|
||||
s64 fade_time_ns;
|
||||
};
|
||||
static_assert(sizeof(Parameters) == 16);
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto parameters = rp.PopRaw<Parameters>();
|
||||
|
||||
LOG_DEBUG(Service_AM, "called. volume={}, fade_time_ns={}", parameters.volume,
|
||||
parameters.fade_time_ns);
|
||||
|
||||
main_applet_volume = std::clamp(parameters.volume, min_allowed_volume, max_allowed_volume);
|
||||
fade_time_ns = std::chrono::nanoseconds{parameters.fade_time_ns};
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void IAudioController::SetTransparentAudioRate(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const float transparent_volume_rate_tmp = rp.Pop<float>();
|
||||
|
||||
LOG_DEBUG(Service_AM, "called. transparent_volume_rate={}", transparent_volume_rate_tmp);
|
||||
|
||||
// Clamp volume range to 0-100%.
|
||||
transparent_volume_rate =
|
||||
std::clamp(transparent_volume_rate_tmp, min_allowed_volume, max_allowed_volume);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
IDisplayController::IDisplayController() : ServiceFramework("IDisplayController") {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include "core/hle/kernel/writable_event.h"
|
||||
@@ -81,8 +82,21 @@ private:
|
||||
void SetExpectedMasterVolume(Kernel::HLERequestContext& ctx);
|
||||
void GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx);
|
||||
void GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx);
|
||||
void ChangeMainAppletMasterVolume(Kernel::HLERequestContext& ctx);
|
||||
void SetTransparentAudioRate(Kernel::HLERequestContext& ctx);
|
||||
|
||||
u32 volume{100};
|
||||
static constexpr float min_allowed_volume = 0.0f;
|
||||
static constexpr float max_allowed_volume = 1.0f;
|
||||
|
||||
float main_applet_volume{0.25f};
|
||||
float library_applet_volume{max_allowed_volume};
|
||||
float transparent_volume_rate{min_allowed_volume};
|
||||
|
||||
// Volume transition fade time in nanoseconds.
|
||||
// e.g. If the main applet volume was 0% and was changed to 50%
|
||||
// with a fade of 50ns, then over the course of 50ns,
|
||||
// the volume will gradually fade up to 50%
|
||||
std::chrono::nanoseconds fade_time_ns{0};
|
||||
};
|
||||
|
||||
class IDisplayController final : public ServiceFramework<IDisplayController> {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include <opus.h>
|
||||
#include <opus_multistream.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -18,12 +19,12 @@
|
||||
namespace Service::Audio {
|
||||
namespace {
|
||||
struct OpusDeleter {
|
||||
void operator()(void* ptr) const {
|
||||
operator delete(ptr);
|
||||
void operator()(OpusMSDecoder* ptr) const {
|
||||
opus_multistream_decoder_destroy(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
using OpusDecoderPtr = std::unique_ptr<OpusDecoder, OpusDeleter>;
|
||||
using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>;
|
||||
|
||||
struct OpusPacketHeader {
|
||||
// Packet size in bytes.
|
||||
@@ -33,7 +34,7 @@ struct OpusPacketHeader {
|
||||
};
|
||||
static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size");
|
||||
|
||||
class OpusDecoderStateBase {
|
||||
class OpusDecoderState {
|
||||
public:
|
||||
/// Describes extra behavior that may be asked of the decoding context.
|
||||
enum class ExtraBehavior {
|
||||
@@ -49,22 +50,13 @@ public:
|
||||
Enabled,
|
||||
};
|
||||
|
||||
virtual ~OpusDecoderStateBase() = default;
|
||||
|
||||
// Decodes interleaved Opus packets. Optionally allows reporting time taken to
|
||||
// perform the decoding, as well as any relevant extra behavior.
|
||||
virtual void DecodeInterleaved(Kernel::HLERequestContext& ctx, PerfTime perf_time,
|
||||
ExtraBehavior extra_behavior) = 0;
|
||||
};
|
||||
|
||||
// Represents the decoder state for a non-multistream decoder.
|
||||
class OpusDecoderState final : public OpusDecoderStateBase {
|
||||
public:
|
||||
explicit OpusDecoderState(OpusDecoderPtr decoder, u32 sample_rate, u32 channel_count)
|
||||
: decoder{std::move(decoder)}, sample_rate{sample_rate}, channel_count{channel_count} {}
|
||||
|
||||
// Decodes interleaved Opus packets. Optionally allows reporting time taken to
|
||||
// perform the decoding, as well as any relevant extra behavior.
|
||||
void DecodeInterleaved(Kernel::HLERequestContext& ctx, PerfTime perf_time,
|
||||
ExtraBehavior extra_behavior) override {
|
||||
ExtraBehavior extra_behavior) {
|
||||
if (perf_time == PerfTime::Disabled) {
|
||||
DecodeInterleavedHelper(ctx, nullptr, extra_behavior);
|
||||
} else {
|
||||
@@ -135,7 +127,7 @@ private:
|
||||
|
||||
const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count));
|
||||
const auto out_sample_count =
|
||||
opus_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0);
|
||||
opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0);
|
||||
if (out_sample_count < 0) {
|
||||
LOG_ERROR(Audio,
|
||||
"Incorrect sample count received from opus_decode, "
|
||||
@@ -158,7 +150,7 @@ private:
|
||||
void ResetDecoderContext() {
|
||||
ASSERT(decoder != nullptr);
|
||||
|
||||
opus_decoder_ctl(decoder.get(), OPUS_RESET_STATE);
|
||||
opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE);
|
||||
}
|
||||
|
||||
OpusDecoderPtr decoder;
|
||||
@@ -168,7 +160,7 @@ private:
|
||||
|
||||
class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> {
|
||||
public:
|
||||
explicit IHardwareOpusDecoderManager(std::unique_ptr<OpusDecoderStateBase> decoder_state)
|
||||
explicit IHardwareOpusDecoderManager(OpusDecoderState decoder_state)
|
||||
: ServiceFramework("IHardwareOpusDecoderManager"), decoder_state{std::move(decoder_state)} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
@@ -190,35 +182,51 @@ private:
|
||||
void DecodeInterleavedOld(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Disabled,
|
||||
OpusDecoderStateBase::ExtraBehavior::None);
|
||||
decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled,
|
||||
OpusDecoderState::ExtraBehavior::None);
|
||||
}
|
||||
|
||||
void DecodeInterleavedWithPerfOld(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Enabled,
|
||||
OpusDecoderStateBase::ExtraBehavior::None);
|
||||
decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled,
|
||||
OpusDecoderState::ExtraBehavior::None);
|
||||
}
|
||||
|
||||
void DecodeInterleaved(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto extra_behavior = rp.Pop<bool>()
|
||||
? OpusDecoderStateBase::ExtraBehavior::ResetContext
|
||||
: OpusDecoderStateBase::ExtraBehavior::None;
|
||||
const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext
|
||||
: OpusDecoderState::ExtraBehavior::None;
|
||||
|
||||
decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Enabled,
|
||||
extra_behavior);
|
||||
decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior);
|
||||
}
|
||||
|
||||
std::unique_ptr<OpusDecoderStateBase> decoder_state;
|
||||
OpusDecoderState decoder_state;
|
||||
};
|
||||
|
||||
std::size_t WorkerBufferSize(u32 channel_count) {
|
||||
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
|
||||
return opus_decoder_get_size(static_cast<int>(channel_count));
|
||||
constexpr int num_streams = 1;
|
||||
const int num_stereo_streams = channel_count == 2 ? 1 : 0;
|
||||
return opus_multistream_decoder_get_size(num_streams, num_stereo_streams);
|
||||
}
|
||||
|
||||
// Creates the mapping table that maps the input channels to the particular
|
||||
// output channels. In the stereo case, we map the left and right input channels
|
||||
// to the left and right output channels respectively.
|
||||
//
|
||||
// However, in the monophonic case, we only map the one available channel
|
||||
// to the sole output channel. We specify 255 for the would-be right channel
|
||||
// as this is a special value defined by Opus to indicate to the decoder to
|
||||
// ignore that channel.
|
||||
std::array<u8, 2> CreateMappingTable(u32 channel_count) {
|
||||
if (channel_count == 2) {
|
||||
return {{0, 1}};
|
||||
}
|
||||
|
||||
return {{0, 255}};
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
@@ -259,9 +267,15 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
|
||||
const std::size_t worker_sz = WorkerBufferSize(channel_count);
|
||||
ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large");
|
||||
|
||||
OpusDecoderPtr decoder{static_cast<OpusDecoder*>(operator new(worker_sz))};
|
||||
if (const int err = opus_decoder_init(decoder.get(), sample_rate, channel_count)) {
|
||||
LOG_ERROR(Audio, "Failed to init opus decoder with error={}", err);
|
||||
const int num_stereo_streams = channel_count == 2 ? 1 : 0;
|
||||
const auto mapping_table = CreateMappingTable(channel_count);
|
||||
|
||||
int error = 0;
|
||||
OpusDecoderPtr decoder{
|
||||
opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1,
|
||||
num_stereo_streams, mapping_table.data(), &error)};
|
||||
if (error != OPUS_OK || decoder == nullptr) {
|
||||
LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(ogniK): Use correct error code
|
||||
rb.Push(ResultCode(-1));
|
||||
@@ -271,7 +285,7 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IHardwareOpusDecoderManager>(
|
||||
std::make_unique<OpusDecoderState>(std::move(decoder), sample_rate, channel_count));
|
||||
OpusDecoderState{std::move(decoder), sample_rate, channel_count});
|
||||
}
|
||||
|
||||
HwOpus::HwOpus() : ServiceFramework("hwopus") {
|
||||
|
||||
@@ -733,7 +733,10 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
|
||||
FSP_SRV::~FSP_SRV() = default;
|
||||
|
||||
void FSP_SRV::SetCurrentProcess(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
current_process_id = rp.Pop<u64>();
|
||||
|
||||
LOG_DEBUG(Service_FS, "called. current_process_id=0x{:016X}", current_process_id);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
@@ -32,6 +32,7 @@ private:
|
||||
void OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
|
||||
|
||||
FileSys::VirtualFile romfs;
|
||||
u64 current_process_id = 0;
|
||||
};
|
||||
|
||||
} // namespace Service::FileSystem
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
|
||||
#include "core/hle/service/nvdrv/devices/nvmap.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
@@ -178,7 +179,7 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou
|
||||
auto& gpu = system_instance.GPU();
|
||||
auto cpu_addr = gpu.MemoryManager().GpuToCpuAddress(params.offset);
|
||||
ASSERT(cpu_addr);
|
||||
gpu.FlushAndInvalidateRegion(*cpu_addr, itr->second.size);
|
||||
gpu.FlushAndInvalidateRegion(ToCacheAddr(Memory::GetPointer(*cpu_addr)), itr->second.size);
|
||||
|
||||
params.offset = gpu.MemoryManager().UnmapBuffer(params.offset, itr->second.size);
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "core/hle/ipc.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/server_port.h"
|
||||
@@ -168,7 +167,7 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co
|
||||
case IPC::CommandType::Close: {
|
||||
IPC::ResponseBuilder rb{context, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
return ResultCode(ErrorModule::HIPC, ErrorDescription::RemoteProcessDead);
|
||||
return IPC::ERR_REMOTE_PROCESS_DEAD;
|
||||
}
|
||||
case IPC::CommandType::ControlWithContext:
|
||||
case IPC::CommandType::Control: {
|
||||
|
||||
@@ -67,7 +67,7 @@ public:
|
||||
if (port == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::static_pointer_cast<T>(port->hle_handler);
|
||||
return std::static_pointer_cast<T>(port->GetHLEHandler());
|
||||
}
|
||||
|
||||
void InvokeControlRequest(Kernel::HLERequestContext& context);
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "core/hle/service/nvdrv/nvdrv.h"
|
||||
#include "core/hle/service/nvflinger/buffer_queue.h"
|
||||
#include "core/hle/service/nvflinger/nvflinger.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/vi/vi.h"
|
||||
#include "core/hle/service/vi/vi_m.h"
|
||||
#include "core/hle/service/vi/vi_s.h"
|
||||
@@ -33,6 +34,7 @@
|
||||
namespace Service::VI {
|
||||
|
||||
constexpr ResultCode ERR_OPERATION_FAILED{ErrorModule::VI, 1};
|
||||
constexpr ResultCode ERR_PERMISSION_DENIED{ErrorModule::VI, 5};
|
||||
constexpr ResultCode ERR_UNSUPPORTED{ErrorModule::VI, 6};
|
||||
constexpr ResultCode ERR_NOT_FOUND{ErrorModule::VI, 7};
|
||||
|
||||
@@ -1203,26 +1205,40 @@ IApplicationDisplayService::IApplicationDisplayService(
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
Module::Interface::Interface(std::shared_ptr<Module> module, const char* name,
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
|
||||
: ServiceFramework(name), module(std::move(module)), nv_flinger(std::move(nv_flinger)) {}
|
||||
static bool IsValidServiceAccess(Permission permission, Policy policy) {
|
||||
if (permission == Permission::User) {
|
||||
return policy == Policy::User;
|
||||
}
|
||||
|
||||
Module::Interface::~Interface() = default;
|
||||
if (permission == Permission::System || permission == Permission::Manager) {
|
||||
return policy == Policy::User || policy == Policy::Compositor;
|
||||
}
|
||||
|
||||
void Module::Interface::GetDisplayService(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_VI, "(STUBBED) called");
|
||||
return false;
|
||||
}
|
||||
|
||||
void detail::GetDisplayServiceImpl(Kernel::HLERequestContext& ctx,
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger,
|
||||
Permission permission) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto policy = rp.PopEnum<Policy>();
|
||||
|
||||
if (!IsValidServiceAccess(permission, policy)) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERR_PERMISSION_DENIED);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IApplicationDisplayService>(nv_flinger);
|
||||
rb.PushIpcInterface<IApplicationDisplayService>(std::move(nv_flinger));
|
||||
}
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager,
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) {
|
||||
auto module = std::make_shared<Module>();
|
||||
std::make_shared<VI_M>(module, nv_flinger)->InstallAsService(service_manager);
|
||||
std::make_shared<VI_S>(module, nv_flinger)->InstallAsService(service_manager);
|
||||
std::make_shared<VI_U>(module, nv_flinger)->InstallAsService(service_manager);
|
||||
std::make_shared<VI_M>(nv_flinger)->InstallAsService(service_manager);
|
||||
std::make_shared<VI_S>(nv_flinger)->InstallAsService(service_manager);
|
||||
std::make_shared<VI_U>(nv_flinger)->InstallAsService(service_manager);
|
||||
}
|
||||
|
||||
} // namespace Service::VI
|
||||
|
||||
@@ -4,12 +4,21 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Kernel {
|
||||
class HLERequestContext;
|
||||
}
|
||||
|
||||
namespace Service::NVFlinger {
|
||||
class NVFlinger;
|
||||
}
|
||||
|
||||
namespace Service::SM {
|
||||
class ServiceManager;
|
||||
}
|
||||
|
||||
namespace Service::VI {
|
||||
|
||||
enum class DisplayResolution : u32 {
|
||||
@@ -19,22 +28,25 @@ enum class DisplayResolution : u32 {
|
||||
UndockedHeight = 720,
|
||||
};
|
||||
|
||||
class Module final {
|
||||
public:
|
||||
class Interface : public ServiceFramework<Interface> {
|
||||
public:
|
||||
explicit Interface(std::shared_ptr<Module> module, const char* name,
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
|
||||
~Interface() override;
|
||||
|
||||
void GetDisplayService(Kernel::HLERequestContext& ctx);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Module> module;
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
|
||||
};
|
||||
/// Permission level for a particular VI service instance
|
||||
enum class Permission {
|
||||
User,
|
||||
System,
|
||||
Manager,
|
||||
};
|
||||
|
||||
/// A policy type that may be requested via GetDisplayService and
|
||||
/// GetDisplayServiceWithProxyNameExchange
|
||||
enum class Policy {
|
||||
User,
|
||||
Compositor,
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
void GetDisplayServiceImpl(Kernel::HLERequestContext& ctx,
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger, Permission permission);
|
||||
} // namespace detail
|
||||
|
||||
/// Registers all VI services with the specified service manager.
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager,
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/service/vi/vi.h"
|
||||
#include "core/hle/service/vi/vi_m.h"
|
||||
|
||||
namespace Service::VI {
|
||||
|
||||
VI_M::VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
|
||||
: Module::Interface(std::move(module), "vi:m", std::move(nv_flinger)) {
|
||||
VI_M::VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
|
||||
: ServiceFramework{"vi:m"}, nv_flinger{std::move(nv_flinger)} {
|
||||
static const FunctionInfo functions[] = {
|
||||
{2, &VI_M::GetDisplayService, "GetDisplayService"},
|
||||
{3, nullptr, "GetDisplayServiceWithProxyNameExchange"},
|
||||
@@ -17,4 +19,10 @@ VI_M::VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger>
|
||||
|
||||
VI_M::~VI_M() = default;
|
||||
|
||||
void VI_M::GetDisplayService(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_VI, "called");
|
||||
|
||||
detail::GetDisplayServiceImpl(ctx, nv_flinger, Permission::Manager);
|
||||
}
|
||||
|
||||
} // namespace Service::VI
|
||||
|
||||
@@ -4,14 +4,27 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/vi/vi.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Kernel {
|
||||
class HLERequestContext;
|
||||
}
|
||||
|
||||
namespace Service::NVFlinger {
|
||||
class NVFlinger;
|
||||
}
|
||||
|
||||
namespace Service::VI {
|
||||
|
||||
class VI_M final : public Module::Interface {
|
||||
class VI_M final : public ServiceFramework<VI_M> {
|
||||
public:
|
||||
explicit VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
|
||||
explicit VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
|
||||
~VI_M() override;
|
||||
|
||||
private:
|
||||
void GetDisplayService(Kernel::HLERequestContext& ctx);
|
||||
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
|
||||
};
|
||||
|
||||
} // namespace Service::VI
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/service/vi/vi.h"
|
||||
#include "core/hle/service/vi/vi_s.h"
|
||||
|
||||
namespace Service::VI {
|
||||
|
||||
VI_S::VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
|
||||
: Module::Interface(std::move(module), "vi:s", std::move(nv_flinger)) {
|
||||
VI_S::VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
|
||||
: ServiceFramework{"vi:s"}, nv_flinger{std::move(nv_flinger)} {
|
||||
static const FunctionInfo functions[] = {
|
||||
{1, &VI_S::GetDisplayService, "GetDisplayService"},
|
||||
{3, nullptr, "GetDisplayServiceWithProxyNameExchange"},
|
||||
@@ -17,4 +19,10 @@ VI_S::VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger>
|
||||
|
||||
VI_S::~VI_S() = default;
|
||||
|
||||
void VI_S::GetDisplayService(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_VI, "called");
|
||||
|
||||
detail::GetDisplayServiceImpl(ctx, nv_flinger, Permission::System);
|
||||
}
|
||||
|
||||
} // namespace Service::VI
|
||||
|
||||
@@ -4,14 +4,27 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/vi/vi.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Kernel {
|
||||
class HLERequestContext;
|
||||
}
|
||||
|
||||
namespace Service::NVFlinger {
|
||||
class NVFlinger;
|
||||
}
|
||||
|
||||
namespace Service::VI {
|
||||
|
||||
class VI_S final : public Module::Interface {
|
||||
class VI_S final : public ServiceFramework<VI_S> {
|
||||
public:
|
||||
explicit VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
|
||||
explicit VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
|
||||
~VI_S() override;
|
||||
|
||||
private:
|
||||
void GetDisplayService(Kernel::HLERequestContext& ctx);
|
||||
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
|
||||
};
|
||||
|
||||
} // namespace Service::VI
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/service/vi/vi.h"
|
||||
#include "core/hle/service/vi/vi_u.h"
|
||||
|
||||
namespace Service::VI {
|
||||
|
||||
VI_U::VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
|
||||
: Module::Interface(std::move(module), "vi:u", std::move(nv_flinger)) {
|
||||
VI_U::VI_U(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
|
||||
: ServiceFramework{"vi:u"}, nv_flinger{std::move(nv_flinger)} {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &VI_U::GetDisplayService, "GetDisplayService"},
|
||||
};
|
||||
@@ -16,4 +18,10 @@ VI_U::VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger>
|
||||
|
||||
VI_U::~VI_U() = default;
|
||||
|
||||
void VI_U::GetDisplayService(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_VI, "called");
|
||||
|
||||
detail::GetDisplayServiceImpl(ctx, nv_flinger, Permission::User);
|
||||
}
|
||||
|
||||
} // namespace Service::VI
|
||||
|
||||
@@ -4,14 +4,27 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/vi/vi.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Kernel {
|
||||
class HLERequestContext;
|
||||
}
|
||||
|
||||
namespace Service::NVFlinger {
|
||||
class NVFlinger;
|
||||
}
|
||||
|
||||
namespace Service::VI {
|
||||
|
||||
class VI_U final : public Module::Interface {
|
||||
class VI_U final : public ServiceFramework<VI_U> {
|
||||
public:
|
||||
explicit VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
|
||||
explicit VI_U(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
|
||||
~VI_U() override;
|
||||
|
||||
private:
|
||||
void GetDisplayService(Kernel::HLERequestContext& ctx);
|
||||
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
|
||||
};
|
||||
|
||||
} // namespace Service::VI
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/loader/linker.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Loader {
|
||||
|
||||
enum class RelocationType : u32 { ABS64 = 257, GLOB_DAT = 1025, JUMP_SLOT = 1026, RELATIVE = 1027 };
|
||||
|
||||
enum DynamicType : u32 {
|
||||
DT_NULL = 0,
|
||||
DT_PLTRELSZ = 2,
|
||||
DT_STRTAB = 5,
|
||||
DT_SYMTAB = 6,
|
||||
DT_RELA = 7,
|
||||
DT_RELASZ = 8,
|
||||
DT_STRSZ = 10,
|
||||
DT_JMPREL = 23,
|
||||
};
|
||||
|
||||
struct Elf64_Rela {
|
||||
u64_le offset;
|
||||
RelocationType type;
|
||||
u32_le symbol;
|
||||
s64_le addend;
|
||||
};
|
||||
static_assert(sizeof(Elf64_Rela) == 0x18, "Elf64_Rela has incorrect size.");
|
||||
|
||||
struct Elf64_Dyn {
|
||||
u64_le tag;
|
||||
u64_le value;
|
||||
};
|
||||
static_assert(sizeof(Elf64_Dyn) == 0x10, "Elf64_Dyn has incorrect size.");
|
||||
|
||||
struct Elf64_Sym {
|
||||
u32_le name;
|
||||
INSERT_PADDING_BYTES(0x2);
|
||||
u16_le shndx;
|
||||
u64_le value;
|
||||
u64_le size;
|
||||
};
|
||||
static_assert(sizeof(Elf64_Sym) == 0x18, "Elf64_Sym has incorrect size.");
|
||||
|
||||
void Linker::WriteRelocations(std::vector<u8>& program_image, const std::vector<Symbol>& symbols,
|
||||
u64 relocation_offset, u64 size, VAddr load_base) {
|
||||
for (u64 i = 0; i < size; i += sizeof(Elf64_Rela)) {
|
||||
Elf64_Rela rela;
|
||||
std::memcpy(&rela, &program_image[relocation_offset + i], sizeof(Elf64_Rela));
|
||||
|
||||
const Symbol& symbol = symbols[rela.symbol];
|
||||
switch (rela.type) {
|
||||
case RelocationType::RELATIVE: {
|
||||
const u64 value = load_base + rela.addend;
|
||||
if (!symbol.name.empty()) {
|
||||
exports[symbol.name] = value;
|
||||
}
|
||||
std::memcpy(&program_image[rela.offset], &value, sizeof(u64));
|
||||
break;
|
||||
}
|
||||
case RelocationType::JUMP_SLOT:
|
||||
case RelocationType::GLOB_DAT:
|
||||
if (!symbol.value) {
|
||||
imports[symbol.name] = {rela.offset + load_base, 0};
|
||||
} else {
|
||||
exports[symbol.name] = symbol.value;
|
||||
std::memcpy(&program_image[rela.offset], &symbol.value, sizeof(u64));
|
||||
}
|
||||
break;
|
||||
case RelocationType::ABS64:
|
||||
if (!symbol.value) {
|
||||
imports[symbol.name] = {rela.offset + load_base, rela.addend};
|
||||
} else {
|
||||
const u64 value = symbol.value + rela.addend;
|
||||
exports[symbol.name] = value;
|
||||
std::memcpy(&program_image[rela.offset], &value, sizeof(u64));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(Loader, "Unknown relocation type: {}", static_cast<int>(rela.type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Linker::Relocate(std::vector<u8>& program_image, u32 dynamic_section_offset, VAddr load_base) {
|
||||
std::map<u64, u64> dynamic;
|
||||
while (dynamic_section_offset < program_image.size()) {
|
||||
Elf64_Dyn dyn;
|
||||
std::memcpy(&dyn, &program_image[dynamic_section_offset], sizeof(Elf64_Dyn));
|
||||
dynamic_section_offset += sizeof(Elf64_Dyn);
|
||||
|
||||
if (dyn.tag == DT_NULL) {
|
||||
break;
|
||||
}
|
||||
dynamic[dyn.tag] = dyn.value;
|
||||
}
|
||||
|
||||
u64 offset = dynamic[DT_SYMTAB];
|
||||
std::vector<Symbol> symbols;
|
||||
while (offset < program_image.size()) {
|
||||
Elf64_Sym sym;
|
||||
std::memcpy(&sym, &program_image[offset], sizeof(Elf64_Sym));
|
||||
offset += sizeof(Elf64_Sym);
|
||||
|
||||
if (sym.name >= dynamic[DT_STRSZ]) {
|
||||
break;
|
||||
}
|
||||
|
||||
std::string name = reinterpret_cast<char*>(&program_image[dynamic[DT_STRTAB] + sym.name]);
|
||||
if (sym.value) {
|
||||
exports[name] = load_base + sym.value;
|
||||
symbols.emplace_back(std::move(name), load_base + sym.value);
|
||||
} else {
|
||||
symbols.emplace_back(std::move(name), 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (dynamic.find(DT_RELA) != dynamic.end()) {
|
||||
WriteRelocations(program_image, symbols, dynamic[DT_RELA], dynamic[DT_RELASZ], load_base);
|
||||
}
|
||||
|
||||
if (dynamic.find(DT_JMPREL) != dynamic.end()) {
|
||||
WriteRelocations(program_image, symbols, dynamic[DT_JMPREL], dynamic[DT_PLTRELSZ],
|
||||
load_base);
|
||||
}
|
||||
}
|
||||
|
||||
void Linker::ResolveImports() {
|
||||
// Resolve imports
|
||||
for (const auto& import : imports) {
|
||||
const auto& search = exports.find(import.first);
|
||||
if (search != exports.end()) {
|
||||
Memory::Write64(import.second.ea, search->second + import.second.addend);
|
||||
} else {
|
||||
LOG_ERROR(Loader, "Unresolved import: {}", import.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Loader
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Loader {
|
||||
|
||||
class Linker {
|
||||
protected:
|
||||
struct Symbol {
|
||||
Symbol(std::string&& name, u64 value) : name(std::move(name)), value(value) {}
|
||||
std::string name;
|
||||
u64 value;
|
||||
};
|
||||
|
||||
struct Import {
|
||||
VAddr ea;
|
||||
s64 addend;
|
||||
};
|
||||
|
||||
void WriteRelocations(std::vector<u8>& program_image, const std::vector<Symbol>& symbols,
|
||||
u64 relocation_offset, u64 size, VAddr load_base);
|
||||
void Relocate(std::vector<u8>& program_image, u32 dynamic_section_offset, VAddr load_base);
|
||||
|
||||
void ResolveImports();
|
||||
|
||||
std::map<std::string, Import> imports;
|
||||
std::map<std::string, VAddr> exports;
|
||||
};
|
||||
|
||||
} // namespace Loader
|
||||
@@ -4,10 +4,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "core/loader/linker.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
@@ -21,7 +21,7 @@ class Process;
|
||||
namespace Loader {
|
||||
|
||||
/// Loads an NRO file
|
||||
class AppLoader_NRO final : public AppLoader, Linker {
|
||||
class AppLoader_NRO final : public AppLoader {
|
||||
public:
|
||||
explicit AppLoader_NRO(FileSys::VirtualFile file);
|
||||
~AppLoader_NRO() override;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
#include <optional>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/loader/linker.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace Kernel {
|
||||
@@ -26,7 +26,7 @@ struct NSOArgumentHeader {
|
||||
static_assert(sizeof(NSOArgumentHeader) == 0x20, "NSOArgumentHeader has incorrect size.");
|
||||
|
||||
/// Loads an NSO file
|
||||
class AppLoader_NSO final : public AppLoader, Linker {
|
||||
class AppLoader_NSO final : public AppLoader {
|
||||
public:
|
||||
explicit AppLoader_NSO(FileSys::VirtualFile file);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/page_table.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/core.h"
|
||||
@@ -18,13 +19,14 @@
|
||||
#include "core/hle/lock.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory_setup.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
namespace Memory {
|
||||
|
||||
static PageTable* current_page_table = nullptr;
|
||||
static Common::PageTable* current_page_table = nullptr;
|
||||
|
||||
void SetCurrentPageTable(PageTable* page_table) {
|
||||
void SetCurrentPageTable(Common::PageTable* page_table) {
|
||||
current_page_table = page_table;
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
@@ -36,39 +38,20 @@ void SetCurrentPageTable(PageTable* page_table) {
|
||||
}
|
||||
}
|
||||
|
||||
PageTable* GetCurrentPageTable() {
|
||||
Common::PageTable* GetCurrentPageTable() {
|
||||
return current_page_table;
|
||||
}
|
||||
|
||||
PageTable::PageTable() = default;
|
||||
|
||||
PageTable::PageTable(std::size_t address_space_width_in_bits) {
|
||||
Resize(address_space_width_in_bits);
|
||||
}
|
||||
|
||||
PageTable::~PageTable() = default;
|
||||
|
||||
void PageTable::Resize(std::size_t address_space_width_in_bits) {
|
||||
const std::size_t num_page_table_entries = 1ULL << (address_space_width_in_bits - PAGE_BITS);
|
||||
|
||||
pointers.resize(num_page_table_entries);
|
||||
attributes.resize(num_page_table_entries);
|
||||
|
||||
// The default is a 39-bit address space, which causes an initial 1GB allocation size. If the
|
||||
// vector size is subsequently decreased (via resize), the vector might not automatically
|
||||
// actually reallocate/resize its underlying allocation, which wastes up to ~800 MB for
|
||||
// 36-bit titles. Call shrink_to_fit to reduce capacity to what's actually in use.
|
||||
|
||||
pointers.shrink_to_fit();
|
||||
attributes.shrink_to_fit();
|
||||
}
|
||||
|
||||
static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, PageType type) {
|
||||
static void MapPages(Common::PageTable& page_table, VAddr base, u64 size, u8* memory,
|
||||
Common::PageType type) {
|
||||
LOG_DEBUG(HW_Memory, "Mapping {} onto {:016X}-{:016X}", fmt::ptr(memory), base * PAGE_SIZE,
|
||||
(base + size) * PAGE_SIZE);
|
||||
|
||||
RasterizerFlushVirtualRegion(base << PAGE_BITS, size * PAGE_SIZE,
|
||||
FlushMode::FlushAndInvalidate);
|
||||
// During boot, current_page_table might not be set yet, in which case we need not flush
|
||||
if (current_page_table) {
|
||||
Core::System::GetInstance().GPU().FlushAndInvalidateRegion(base << PAGE_BITS,
|
||||
size * PAGE_SIZE);
|
||||
}
|
||||
|
||||
VAddr end = base + size;
|
||||
ASSERT_MSG(end <= page_table.pointers.size(), "out of range mapping at {:016X}",
|
||||
@@ -88,41 +71,47 @@ static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, Pa
|
||||
}
|
||||
}
|
||||
|
||||
void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target) {
|
||||
void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, u8* target) {
|
||||
ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size);
|
||||
ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base);
|
||||
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, PageType::Memory);
|
||||
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, Common::PageType::Memory);
|
||||
}
|
||||
|
||||
void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer mmio_handler) {
|
||||
void MapIoRegion(Common::PageTable& page_table, VAddr base, u64 size,
|
||||
Common::MemoryHookPointer mmio_handler) {
|
||||
ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size);
|
||||
ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base);
|
||||
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Special);
|
||||
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, Common::PageType::Special);
|
||||
|
||||
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
|
||||
SpecialRegion region{SpecialRegion::Type::IODevice, std::move(mmio_handler)};
|
||||
page_table.special_regions.add(std::make_pair(interval, std::set<SpecialRegion>{region}));
|
||||
Common::SpecialRegion region{Common::SpecialRegion::Type::IODevice, std::move(mmio_handler)};
|
||||
page_table.special_regions.add(
|
||||
std::make_pair(interval, std::set<Common::SpecialRegion>{region}));
|
||||
}
|
||||
|
||||
void UnmapRegion(PageTable& page_table, VAddr base, u64 size) {
|
||||
void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) {
|
||||
ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size);
|
||||
ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base);
|
||||
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Unmapped);
|
||||
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, Common::PageType::Unmapped);
|
||||
|
||||
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
|
||||
page_table.special_regions.erase(interval);
|
||||
}
|
||||
|
||||
void AddDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook) {
|
||||
void AddDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
|
||||
Common::MemoryHookPointer hook) {
|
||||
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
|
||||
SpecialRegion region{SpecialRegion::Type::DebugHook, std::move(hook)};
|
||||
page_table.special_regions.add(std::make_pair(interval, std::set<SpecialRegion>{region}));
|
||||
Common::SpecialRegion region{Common::SpecialRegion::Type::DebugHook, std::move(hook)};
|
||||
page_table.special_regions.add(
|
||||
std::make_pair(interval, std::set<Common::SpecialRegion>{region}));
|
||||
}
|
||||
|
||||
void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook) {
|
||||
void RemoveDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
|
||||
Common::MemoryHookPointer hook) {
|
||||
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
|
||||
SpecialRegion region{SpecialRegion::Type::DebugHook, std::move(hook)};
|
||||
page_table.special_regions.subtract(std::make_pair(interval, std::set<SpecialRegion>{region}));
|
||||
Common::SpecialRegion region{Common::SpecialRegion::Type::DebugHook, std::move(hook)};
|
||||
page_table.special_regions.subtract(
|
||||
std::make_pair(interval, std::set<Common::SpecialRegion>{region}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,19 +160,19 @@ T Read(const VAddr vaddr) {
|
||||
return value;
|
||||
}
|
||||
|
||||
PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
switch (type) {
|
||||
case PageType::Unmapped:
|
||||
case Common::PageType::Unmapped:
|
||||
LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:08X}", sizeof(T) * 8, vaddr);
|
||||
return 0;
|
||||
case PageType::Memory:
|
||||
case Common::PageType::Memory:
|
||||
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr);
|
||||
break;
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush);
|
||||
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
auto host_ptr{GetPointerFromVMA(vaddr)};
|
||||
Core::System::GetInstance().GPU().FlushRegion(ToCacheAddr(host_ptr), sizeof(T));
|
||||
T value;
|
||||
std::memcpy(&value, GetPointerFromVMA(vaddr), sizeof(T));
|
||||
std::memcpy(&value, host_ptr, sizeof(T));
|
||||
return value;
|
||||
}
|
||||
default:
|
||||
@@ -201,18 +190,19 @@ void Write(const VAddr vaddr, const T data) {
|
||||
return;
|
||||
}
|
||||
|
||||
PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
switch (type) {
|
||||
case PageType::Unmapped:
|
||||
case Common::PageType::Unmapped:
|
||||
LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8,
|
||||
static_cast<u32>(data), vaddr);
|
||||
return;
|
||||
case PageType::Memory:
|
||||
case Common::PageType::Memory:
|
||||
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr);
|
||||
break;
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Invalidate);
|
||||
std::memcpy(GetPointerFromVMA(vaddr), &data, sizeof(T));
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
auto host_ptr{GetPointerFromVMA(vaddr)};
|
||||
Core::System::GetInstance().GPU().InvalidateRegion(ToCacheAddr(host_ptr), sizeof(T));
|
||||
std::memcpy(host_ptr, &data, sizeof(T));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -227,10 +217,10 @@ bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) {
|
||||
if (page_pointer)
|
||||
return true;
|
||||
|
||||
if (page_table.attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory)
|
||||
if (page_table.attributes[vaddr >> PAGE_BITS] == Common::PageType::RasterizerCachedMemory)
|
||||
return true;
|
||||
|
||||
if (page_table.attributes[vaddr >> PAGE_BITS] != PageType::Special)
|
||||
if (page_table.attributes[vaddr >> PAGE_BITS] != Common::PageType::Special)
|
||||
return false;
|
||||
|
||||
return false;
|
||||
@@ -250,7 +240,8 @@ u8* GetPointer(const VAddr vaddr) {
|
||||
return page_pointer + (vaddr & PAGE_MASK);
|
||||
}
|
||||
|
||||
if (current_page_table->attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory) {
|
||||
if (current_page_table->attributes[vaddr >> PAGE_BITS] ==
|
||||
Common::PageType::RasterizerCachedMemory) {
|
||||
return GetPointerFromVMA(vaddr);
|
||||
}
|
||||
|
||||
@@ -284,20 +275,20 @@ void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
|
||||
|
||||
u64 num_pages = ((vaddr + size - 1) >> PAGE_BITS) - (vaddr >> PAGE_BITS) + 1;
|
||||
for (unsigned i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) {
|
||||
PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
Common::PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
|
||||
if (cached) {
|
||||
// Switch page type to cached if now cached
|
||||
switch (page_type) {
|
||||
case PageType::Unmapped:
|
||||
case Common::PageType::Unmapped:
|
||||
// It is not necessary for a process to have this region mapped into its address
|
||||
// space, for example, a system module need not have a VRAM mapping.
|
||||
break;
|
||||
case PageType::Memory:
|
||||
page_type = PageType::RasterizerCachedMemory;
|
||||
case Common::PageType::Memory:
|
||||
page_type = Common::PageType::RasterizerCachedMemory;
|
||||
current_page_table->pointers[vaddr >> PAGE_BITS] = nullptr;
|
||||
break;
|
||||
case PageType::RasterizerCachedMemory:
|
||||
case Common::PageType::RasterizerCachedMemory:
|
||||
// There can be more than one GPU region mapped per CPU region, so it's common that
|
||||
// this area is already marked as cached.
|
||||
break;
|
||||
@@ -307,23 +298,23 @@ void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
|
||||
} else {
|
||||
// Switch page type to uncached if now uncached
|
||||
switch (page_type) {
|
||||
case PageType::Unmapped:
|
||||
case Common::PageType::Unmapped:
|
||||
// It is not necessary for a process to have this region mapped into its address
|
||||
// space, for example, a system module need not have a VRAM mapping.
|
||||
break;
|
||||
case PageType::Memory:
|
||||
case Common::PageType::Memory:
|
||||
// There can be more than one GPU region mapped per CPU region, so it's common that
|
||||
// this area is already unmarked as cached.
|
||||
break;
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
u8* pointer = GetPointerFromVMA(vaddr & ~PAGE_MASK);
|
||||
if (pointer == nullptr) {
|
||||
// It's possible that this function has been called while updating the pagetable
|
||||
// after unmapping a VMA. In that case the underlying VMA will no longer exist,
|
||||
// and we should just leave the pagetable entry blank.
|
||||
page_type = PageType::Unmapped;
|
||||
page_type = Common::PageType::Unmapped;
|
||||
} else {
|
||||
page_type = PageType::Memory;
|
||||
page_type = Common::PageType::Memory;
|
||||
current_page_table->pointers[vaddr >> PAGE_BITS] = pointer;
|
||||
}
|
||||
break;
|
||||
@@ -335,47 +326,6 @@ void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode) {
|
||||
auto& system_instance = Core::System::GetInstance();
|
||||
|
||||
// Since pages are unmapped on shutdown after video core is shutdown, the renderer may be
|
||||
// null here
|
||||
if (!system_instance.IsPoweredOn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const VAddr end = start + size;
|
||||
|
||||
const auto CheckRegion = [&](VAddr region_start, VAddr region_end) {
|
||||
if (start >= region_end || end <= region_start) {
|
||||
// No overlap with region
|
||||
return;
|
||||
}
|
||||
|
||||
const VAddr overlap_start = std::max(start, region_start);
|
||||
const VAddr overlap_end = std::min(end, region_end);
|
||||
const VAddr overlap_size = overlap_end - overlap_start;
|
||||
|
||||
auto& gpu = system_instance.GPU();
|
||||
switch (mode) {
|
||||
case FlushMode::Flush:
|
||||
gpu.FlushRegion(overlap_start, overlap_size);
|
||||
break;
|
||||
case FlushMode::Invalidate:
|
||||
gpu.InvalidateRegion(overlap_start, overlap_size);
|
||||
break;
|
||||
case FlushMode::FlushAndInvalidate:
|
||||
gpu.FlushAndInvalidateRegion(overlap_start, overlap_size);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const auto& vm_manager = Core::CurrentProcess()->VMManager();
|
||||
|
||||
CheckRegion(vm_manager.GetCodeRegionBaseAddress(), vm_manager.GetCodeRegionEndAddress());
|
||||
CheckRegion(vm_manager.GetHeapRegionBaseAddress(), vm_manager.GetHeapRegionEndAddress());
|
||||
}
|
||||
|
||||
u8 Read8(const VAddr addr) {
|
||||
return Read<u8>(addr);
|
||||
}
|
||||
@@ -406,24 +356,24 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_
|
||||
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
|
||||
|
||||
switch (page_table.attributes[page_index]) {
|
||||
case PageType::Unmapped: {
|
||||
case Common::PageType::Unmapped: {
|
||||
LOG_ERROR(HW_Memory,
|
||||
"Unmapped ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
|
||||
current_vaddr, src_addr, size);
|
||||
std::memset(dest_buffer, 0, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::Memory: {
|
||||
case Common::PageType::Memory: {
|
||||
DEBUG_ASSERT(page_table.pointers[page_index]);
|
||||
|
||||
const u8* src_ptr = page_table.pointers[page_index] + page_offset;
|
||||
std::memcpy(dest_buffer, src_ptr, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
||||
FlushMode::Flush);
|
||||
std::memcpy(dest_buffer, GetPointerFromVMA(process, current_vaddr), copy_amount);
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)};
|
||||
Core::System::GetInstance().GPU().FlushRegion(ToCacheAddr(host_ptr), copy_amount);
|
||||
std::memcpy(dest_buffer, host_ptr, copy_amount);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -470,23 +420,23 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi
|
||||
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
|
||||
|
||||
switch (page_table.attributes[page_index]) {
|
||||
case PageType::Unmapped: {
|
||||
case Common::PageType::Unmapped: {
|
||||
LOG_ERROR(HW_Memory,
|
||||
"Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
|
||||
current_vaddr, dest_addr, size);
|
||||
break;
|
||||
}
|
||||
case PageType::Memory: {
|
||||
case Common::PageType::Memory: {
|
||||
DEBUG_ASSERT(page_table.pointers[page_index]);
|
||||
|
||||
u8* dest_ptr = page_table.pointers[page_index] + page_offset;
|
||||
std::memcpy(dest_ptr, src_buffer, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
||||
FlushMode::Invalidate);
|
||||
std::memcpy(GetPointerFromVMA(process, current_vaddr), src_buffer, copy_amount);
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)};
|
||||
Core::System::GetInstance().GPU().InvalidateRegion(ToCacheAddr(host_ptr), copy_amount);
|
||||
std::memcpy(host_ptr, src_buffer, copy_amount);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -516,23 +466,23 @@ void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const std:
|
||||
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
|
||||
|
||||
switch (page_table.attributes[page_index]) {
|
||||
case PageType::Unmapped: {
|
||||
case Common::PageType::Unmapped: {
|
||||
LOG_ERROR(HW_Memory,
|
||||
"Unmapped ZeroBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
|
||||
current_vaddr, dest_addr, size);
|
||||
break;
|
||||
}
|
||||
case PageType::Memory: {
|
||||
case Common::PageType::Memory: {
|
||||
DEBUG_ASSERT(page_table.pointers[page_index]);
|
||||
|
||||
u8* dest_ptr = page_table.pointers[page_index] + page_offset;
|
||||
std::memset(dest_ptr, 0, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
||||
FlushMode::Invalidate);
|
||||
std::memset(GetPointerFromVMA(process, current_vaddr), 0, copy_amount);
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)};
|
||||
Core::System::GetInstance().GPU().InvalidateRegion(ToCacheAddr(host_ptr), copy_amount);
|
||||
std::memset(host_ptr, 0, copy_amount);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -558,23 +508,23 @@ void CopyBlock(const Kernel::Process& process, VAddr dest_addr, VAddr src_addr,
|
||||
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
|
||||
|
||||
switch (page_table.attributes[page_index]) {
|
||||
case PageType::Unmapped: {
|
||||
case Common::PageType::Unmapped: {
|
||||
LOG_ERROR(HW_Memory,
|
||||
"Unmapped CopyBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
|
||||
current_vaddr, src_addr, size);
|
||||
ZeroBlock(process, dest_addr, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::Memory: {
|
||||
case Common::PageType::Memory: {
|
||||
DEBUG_ASSERT(page_table.pointers[page_index]);
|
||||
const u8* src_ptr = page_table.pointers[page_index] + page_offset;
|
||||
WriteBlock(process, dest_addr, src_ptr, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
||||
FlushMode::Flush);
|
||||
WriteBlock(process, dest_addr, GetPointerFromVMA(process, current_vaddr), copy_amount);
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)};
|
||||
Core::System::GetInstance().GPU().FlushRegion(ToCacheAddr(host_ptr), copy_amount);
|
||||
WriteBlock(process, dest_addr, host_ptr, copy_amount);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
#include <vector>
|
||||
#include <boost/icl/interval_map.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/memory_hook.h"
|
||||
|
||||
namespace Common {
|
||||
struct PageTable;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
class Process;
|
||||
@@ -26,71 +29,6 @@ constexpr std::size_t PAGE_BITS = 12;
|
||||
constexpr u64 PAGE_SIZE = 1ULL << PAGE_BITS;
|
||||
constexpr u64 PAGE_MASK = PAGE_SIZE - 1;
|
||||
|
||||
enum class PageType : u8 {
|
||||
/// Page is unmapped and should cause an access error.
|
||||
Unmapped,
|
||||
/// Page is mapped to regular memory. This is the only type you can get pointers to.
|
||||
Memory,
|
||||
/// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
|
||||
/// invalidation
|
||||
RasterizerCachedMemory,
|
||||
/// Page is mapped to a I/O region. Writing and reading to this page is handled by functions.
|
||||
Special,
|
||||
};
|
||||
|
||||
struct SpecialRegion {
|
||||
enum class Type {
|
||||
DebugHook,
|
||||
IODevice,
|
||||
} type;
|
||||
|
||||
MemoryHookPointer handler;
|
||||
|
||||
bool operator<(const SpecialRegion& other) const {
|
||||
return std::tie(type, handler) < std::tie(other.type, other.handler);
|
||||
}
|
||||
|
||||
bool operator==(const SpecialRegion& other) const {
|
||||
return std::tie(type, handler) == std::tie(other.type, other.handler);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A (reasonably) fast way of allowing switchable and remappable process address spaces. It loosely
|
||||
* mimics the way a real CPU page table works.
|
||||
*/
|
||||
struct PageTable {
|
||||
explicit PageTable();
|
||||
explicit PageTable(std::size_t address_space_width_in_bits);
|
||||
~PageTable();
|
||||
|
||||
/**
|
||||
* Resizes the page table to be able to accomodate enough pages within
|
||||
* a given address space.
|
||||
*
|
||||
* @param address_space_width_in_bits The address size width in bits.
|
||||
*/
|
||||
void Resize(std::size_t address_space_width_in_bits);
|
||||
|
||||
/**
|
||||
* Vector of memory pointers backing each page. An entry can only be non-null if the
|
||||
* corresponding entry in the `attributes` vector is of type `Memory`.
|
||||
*/
|
||||
std::vector<u8*> pointers;
|
||||
|
||||
/**
|
||||
* Contains MMIO handlers that back memory regions whose entries in the `attribute` vector is
|
||||
* of type `Special`.
|
||||
*/
|
||||
boost::icl::interval_map<VAddr, std::set<SpecialRegion>> special_regions;
|
||||
|
||||
/**
|
||||
* Vector of fine grained page attributes. If it is set to any value other than `Memory`, then
|
||||
* the corresponding entry in `pointers` MUST be set to null.
|
||||
*/
|
||||
std::vector<PageType> attributes;
|
||||
};
|
||||
|
||||
/// Virtual user-space memory regions
|
||||
enum : VAddr {
|
||||
/// Read-only page containing kernel and system configuration values.
|
||||
@@ -116,8 +54,8 @@ enum : VAddr {
|
||||
};
|
||||
|
||||
/// Currently active page table
|
||||
void SetCurrentPageTable(PageTable* page_table);
|
||||
PageTable* GetCurrentPageTable();
|
||||
void SetCurrentPageTable(Common::PageTable* page_table);
|
||||
Common::PageTable* GetCurrentPageTable();
|
||||
|
||||
/// Determines if the given VAddr is valid for the specified process.
|
||||
bool IsValidVirtualAddress(const Kernel::Process& process, VAddr vaddr);
|
||||
@@ -161,10 +99,4 @@ enum class FlushMode {
|
||||
*/
|
||||
void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached);
|
||||
|
||||
/**
|
||||
* Flushes and invalidates any externally cached rasterizer resources touching the given virtual
|
||||
* address region.
|
||||
*/
|
||||
void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode);
|
||||
|
||||
} // namespace Memory
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/memory_hook.h"
|
||||
#include "common/memory_hook.h"
|
||||
|
||||
namespace Common {
|
||||
struct PageTable;
|
||||
}
|
||||
|
||||
namespace Memory {
|
||||
|
||||
@@ -17,7 +21,7 @@ namespace Memory {
|
||||
* @param size The amount of bytes to map. Must be page-aligned.
|
||||
* @param target Buffer with the memory backing the mapping. Must be of length at least `size`.
|
||||
*/
|
||||
void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target);
|
||||
void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, u8* target);
|
||||
|
||||
/**
|
||||
* Maps a region of the emulated process address space as a IO region.
|
||||
@@ -26,11 +30,14 @@ void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target);
|
||||
* @param size The amount of bytes to map. Must be page-aligned.
|
||||
* @param mmio_handler The handler that backs the mapping.
|
||||
*/
|
||||
void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer mmio_handler);
|
||||
void MapIoRegion(Common::PageTable& page_table, VAddr base, u64 size,
|
||||
Common::MemoryHookPointer mmio_handler);
|
||||
|
||||
void UnmapRegion(PageTable& page_table, VAddr base, u64 size);
|
||||
void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size);
|
||||
|
||||
void AddDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook);
|
||||
void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook);
|
||||
void AddDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
|
||||
Common::MemoryHookPointer hook);
|
||||
void RemoveDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
|
||||
Common::MemoryHookPointer hook);
|
||||
|
||||
} // namespace Memory
|
||||
|
||||
@@ -7,15 +7,18 @@ add_library(input_common STATIC
|
||||
main.h
|
||||
motion_emu.cpp
|
||||
motion_emu.h
|
||||
|
||||
$<$<BOOL:${SDL2_FOUND}>:sdl/sdl.cpp sdl/sdl.h>
|
||||
sdl/sdl.cpp
|
||||
sdl/sdl.h
|
||||
)
|
||||
|
||||
create_target_directory_groups(input_common)
|
||||
|
||||
target_link_libraries(input_common PUBLIC core PRIVATE common)
|
||||
|
||||
if(SDL2_FOUND)
|
||||
target_sources(input_common PRIVATE
|
||||
sdl/sdl_impl.cpp
|
||||
sdl/sdl_impl.h
|
||||
)
|
||||
target_link_libraries(input_common PRIVATE SDL2)
|
||||
target_compile_definitions(input_common PRIVATE HAVE_SDL2)
|
||||
endif()
|
||||
|
||||
create_target_directory_groups(input_common)
|
||||
target_link_libraries(input_common PUBLIC core PRIVATE common)
|
||||
|
||||
@@ -17,10 +17,7 @@ namespace InputCommon {
|
||||
|
||||
static std::shared_ptr<Keyboard> keyboard;
|
||||
static std::shared_ptr<MotionEmu> motion_emu;
|
||||
|
||||
#ifdef HAVE_SDL2
|
||||
static std::thread poll_thread;
|
||||
#endif
|
||||
static std::unique_ptr<SDL::State> sdl;
|
||||
|
||||
void Init() {
|
||||
keyboard = std::make_shared<Keyboard>();
|
||||
@@ -30,15 +27,7 @@ void Init() {
|
||||
motion_emu = std::make_shared<MotionEmu>();
|
||||
Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
|
||||
|
||||
#ifdef HAVE_SDL2
|
||||
SDL::Init();
|
||||
#endif
|
||||
}
|
||||
|
||||
void StartJoystickEventHandler() {
|
||||
#ifdef HAVE_SDL2
|
||||
poll_thread = std::thread(SDL::PollLoop);
|
||||
#endif
|
||||
sdl = SDL::Init();
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
@@ -47,11 +36,7 @@ void Shutdown() {
|
||||
Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
|
||||
Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
|
||||
motion_emu.reset();
|
||||
|
||||
#ifdef HAVE_SDL2
|
||||
SDL::Shutdown();
|
||||
poll_thread.join();
|
||||
#endif
|
||||
sdl.reset();
|
||||
}
|
||||
|
||||
Keyboard* GetKeyboard() {
|
||||
@@ -88,7 +73,7 @@ namespace Polling {
|
||||
|
||||
std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) {
|
||||
#ifdef HAVE_SDL2
|
||||
return SDL::Polling::GetPollers(type);
|
||||
return sdl->GetPollers(type);
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
|
||||
@@ -20,8 +20,6 @@ void Init();
|
||||
/// Deregisters all built-in input device factories and shuts them down.
|
||||
void Shutdown();
|
||||
|
||||
void StartJoystickEventHandler();
|
||||
|
||||
class Keyboard;
|
||||
|
||||
/// Gets the keyboard button device factory.
|
||||
|
||||
@@ -1,631 +1,19 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <SDL.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/math_util.h"
|
||||
#include "common/param_package.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
#include "input_common/main.h"
|
||||
#include "input_common/sdl/sdl.h"
|
||||
#ifdef HAVE_SDL2
|
||||
#include "input_common/sdl/sdl_impl.h"
|
||||
#endif
|
||||
|
||||
namespace InputCommon {
|
||||
namespace InputCommon::SDL {
|
||||
|
||||
namespace SDL {
|
||||
|
||||
class SDLJoystick;
|
||||
class SDLButtonFactory;
|
||||
class SDLAnalogFactory;
|
||||
|
||||
/// Map of GUID of a list of corresponding virtual Joysticks
|
||||
static std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
|
||||
static std::mutex joystick_map_mutex;
|
||||
|
||||
static std::shared_ptr<SDLButtonFactory> button_factory;
|
||||
static std::shared_ptr<SDLAnalogFactory> analog_factory;
|
||||
|
||||
/// Used by the Pollers during config
|
||||
static std::atomic<bool> polling;
|
||||
static Common::SPSCQueue<SDL_Event> event_queue;
|
||||
|
||||
static std::atomic<bool> initialized = false;
|
||||
|
||||
static std::string GetGUID(SDL_Joystick* joystick) {
|
||||
SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
|
||||
char guid_str[33];
|
||||
SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
|
||||
return guid_str;
|
||||
std::unique_ptr<State> Init() {
|
||||
#ifdef HAVE_SDL2
|
||||
return std::make_unique<SDLState>();
|
||||
#else
|
||||
return std::make_unique<NullState>();
|
||||
#endif
|
||||
}
|
||||
|
||||
class SDLJoystick {
|
||||
public:
|
||||
SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
|
||||
decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose)
|
||||
: guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, deleter} {}
|
||||
|
||||
void SetButton(int button, bool value) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
state.buttons[button] = value;
|
||||
}
|
||||
|
||||
bool GetButton(int button) const {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
return state.buttons.at(button);
|
||||
}
|
||||
|
||||
void SetAxis(int axis, Sint16 value) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
state.axes[axis] = value;
|
||||
}
|
||||
|
||||
float GetAxis(int axis) const {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
return state.axes.at(axis) / 32767.0f;
|
||||
}
|
||||
|
||||
std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const {
|
||||
float x = GetAxis(axis_x);
|
||||
float y = GetAxis(axis_y);
|
||||
y = -y; // 3DS uses an y-axis inverse from SDL
|
||||
|
||||
// Make sure the coordinates are in the unit circle,
|
||||
// otherwise normalize it.
|
||||
float r = x * x + y * y;
|
||||
if (r > 1.0f) {
|
||||
r = std::sqrt(r);
|
||||
x /= r;
|
||||
y /= r;
|
||||
}
|
||||
|
||||
return std::make_tuple(x, y);
|
||||
}
|
||||
|
||||
void SetHat(int hat, Uint8 direction) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
state.hats[hat] = direction;
|
||||
}
|
||||
|
||||
bool GetHatDirection(int hat, Uint8 direction) const {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
return (state.hats.at(hat) & direction) != 0;
|
||||
}
|
||||
/**
|
||||
* The guid of the joystick
|
||||
*/
|
||||
const std::string& GetGUID() const {
|
||||
return guid;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of joystick from the same type that were connected before this joystick
|
||||
*/
|
||||
int GetPort() const {
|
||||
return port;
|
||||
}
|
||||
|
||||
SDL_Joystick* GetSDLJoystick() const {
|
||||
return sdl_joystick.get();
|
||||
}
|
||||
|
||||
void SetSDLJoystick(SDL_Joystick* joystick,
|
||||
decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) {
|
||||
sdl_joystick =
|
||||
std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)>(joystick, deleter);
|
||||
}
|
||||
|
||||
private:
|
||||
struct State {
|
||||
std::unordered_map<int, bool> buttons;
|
||||
std::unordered_map<int, Sint16> axes;
|
||||
std::unordered_map<int, Uint8> hats;
|
||||
} state;
|
||||
std::string guid;
|
||||
int port;
|
||||
std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
|
||||
mutable std::mutex mutex;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the nth joystick with the corresponding GUID
|
||||
*/
|
||||
static std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port) {
|
||||
std::lock_guard<std::mutex> lock(joystick_map_mutex);
|
||||
const auto it = joystick_map.find(guid);
|
||||
if (it != joystick_map.end()) {
|
||||
while (it->second.size() <= port) {
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, it->second.size(), nullptr,
|
||||
[](SDL_Joystick*) {});
|
||||
it->second.emplace_back(std::move(joystick));
|
||||
}
|
||||
return it->second[port];
|
||||
}
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, [](SDL_Joystick*) {});
|
||||
return joystick_map[guid].emplace_back(std::move(joystick));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check how many identical joysticks (by guid) were connected before the one with sdl_id and so tie
|
||||
* it to a SDLJoystick with the same guid and that port
|
||||
*/
|
||||
static std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
|
||||
std::lock_guard<std::mutex> lock(joystick_map_mutex);
|
||||
auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
|
||||
const std::string guid = GetGUID(sdl_joystick);
|
||||
auto map_it = joystick_map.find(guid);
|
||||
if (map_it != joystick_map.end()) {
|
||||
auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(),
|
||||
[&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) {
|
||||
return sdl_joystick == joystick->GetSDLJoystick();
|
||||
});
|
||||
if (vec_it != map_it->second.end()) {
|
||||
// This is the common case: There is already an existing SDL_Joystick maped to a
|
||||
// SDLJoystick. return the SDLJoystick
|
||||
return *vec_it;
|
||||
}
|
||||
// Search for a SDLJoystick without a mapped SDL_Joystick...
|
||||
auto nullptr_it = std::find_if(map_it->second.begin(), map_it->second.end(),
|
||||
[](const std::shared_ptr<SDLJoystick>& joystick) {
|
||||
return !joystick->GetSDLJoystick();
|
||||
});
|
||||
if (nullptr_it != map_it->second.end()) {
|
||||
// ... and map it
|
||||
(*nullptr_it)->SetSDLJoystick(sdl_joystick);
|
||||
return *nullptr_it;
|
||||
}
|
||||
// There is no SDLJoystick without a mapped SDL_Joystick
|
||||
// Create a new SDLJoystick
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, map_it->second.size(), sdl_joystick);
|
||||
return map_it->second.emplace_back(std::move(joystick));
|
||||
}
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
|
||||
return joystick_map[guid].emplace_back(std::move(joystick));
|
||||
}
|
||||
|
||||
void InitJoystick(int joystick_index) {
|
||||
std::lock_guard<std::mutex> lock(joystick_map_mutex);
|
||||
SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
|
||||
if (!sdl_joystick) {
|
||||
LOG_ERROR(Input, "failed to open joystick {}", joystick_index);
|
||||
return;
|
||||
}
|
||||
std::string guid = GetGUID(sdl_joystick);
|
||||
if (joystick_map.find(guid) == joystick_map.end()) {
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
|
||||
joystick_map[guid].emplace_back(std::move(joystick));
|
||||
return;
|
||||
}
|
||||
auto& joystick_guid_list = joystick_map[guid];
|
||||
const auto it = std::find_if(
|
||||
joystick_guid_list.begin(), joystick_guid_list.end(),
|
||||
[](const std::shared_ptr<SDLJoystick>& joystick) { return !joystick->GetSDLJoystick(); });
|
||||
if (it != joystick_guid_list.end()) {
|
||||
(*it)->SetSDLJoystick(sdl_joystick);
|
||||
return;
|
||||
}
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, joystick_guid_list.size(), sdl_joystick);
|
||||
joystick_guid_list.emplace_back(std::move(joystick));
|
||||
}
|
||||
|
||||
void CloseJoystick(SDL_Joystick* sdl_joystick) {
|
||||
std::lock_guard<std::mutex> lock(joystick_map_mutex);
|
||||
std::string guid = GetGUID(sdl_joystick);
|
||||
// This call to guid is save since the joystick is guranteed to be in that map
|
||||
auto& joystick_guid_list = joystick_map[guid];
|
||||
const auto joystick_it =
|
||||
std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
|
||||
[&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) {
|
||||
return joystick->GetSDLJoystick() == sdl_joystick;
|
||||
});
|
||||
(*joystick_it)->SetSDLJoystick(nullptr, [](SDL_Joystick*) {});
|
||||
}
|
||||
|
||||
void HandleGameControllerEvent(const SDL_Event& event) {
|
||||
switch (event.type) {
|
||||
case SDL_JOYBUTTONUP: {
|
||||
auto joystick = GetSDLJoystickBySDLID(event.jbutton.which);
|
||||
if (joystick) {
|
||||
joystick->SetButton(event.jbutton.button, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_JOYBUTTONDOWN: {
|
||||
auto joystick = GetSDLJoystickBySDLID(event.jbutton.which);
|
||||
if (joystick) {
|
||||
joystick->SetButton(event.jbutton.button, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_JOYHATMOTION: {
|
||||
auto joystick = GetSDLJoystickBySDLID(event.jhat.which);
|
||||
if (joystick) {
|
||||
joystick->SetHat(event.jhat.hat, event.jhat.value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_JOYAXISMOTION: {
|
||||
auto joystick = GetSDLJoystickBySDLID(event.jaxis.which);
|
||||
if (joystick) {
|
||||
joystick->SetAxis(event.jaxis.axis, event.jaxis.value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_JOYDEVICEREMOVED:
|
||||
LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
|
||||
CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
|
||||
break;
|
||||
case SDL_JOYDEVICEADDED:
|
||||
LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which);
|
||||
InitJoystick(event.jdevice.which);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CloseSDLJoysticks() {
|
||||
std::lock_guard<std::mutex> lock(joystick_map_mutex);
|
||||
joystick_map.clear();
|
||||
}
|
||||
|
||||
void PollLoop() {
|
||||
if (SDL_Init(SDL_INIT_JOYSTICK) < 0) {
|
||||
LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Event event;
|
||||
while (initialized) {
|
||||
// Wait for 10 ms or until an event happens
|
||||
if (SDL_WaitEventTimeout(&event, 10)) {
|
||||
// Don't handle the event if we are configuring
|
||||
if (polling) {
|
||||
event_queue.Push(event);
|
||||
} else {
|
||||
HandleGameControllerEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
CloseSDLJoysticks();
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
}
|
||||
|
||||
class SDLButton final : public Input::ButtonDevice {
|
||||
public:
|
||||
explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_)
|
||||
: joystick(std::move(joystick_)), button(button_) {}
|
||||
|
||||
bool GetStatus() const override {
|
||||
return joystick->GetButton(button);
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<SDLJoystick> joystick;
|
||||
int button;
|
||||
};
|
||||
|
||||
class SDLDirectionButton final : public Input::ButtonDevice {
|
||||
public:
|
||||
explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
|
||||
: joystick(std::move(joystick_)), hat(hat_), direction(direction_) {}
|
||||
|
||||
bool GetStatus() const override {
|
||||
return joystick->GetHatDirection(hat, direction);
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<SDLJoystick> joystick;
|
||||
int hat;
|
||||
Uint8 direction;
|
||||
};
|
||||
|
||||
class SDLAxisButton final : public Input::ButtonDevice {
|
||||
public:
|
||||
explicit SDLAxisButton(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_,
|
||||
bool trigger_if_greater_)
|
||||
: joystick(std::move(joystick_)), axis(axis_), threshold(threshold_),
|
||||
trigger_if_greater(trigger_if_greater_) {}
|
||||
|
||||
bool GetStatus() const override {
|
||||
float axis_value = joystick->GetAxis(axis);
|
||||
if (trigger_if_greater)
|
||||
return axis_value > threshold;
|
||||
return axis_value < threshold;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<SDLJoystick> joystick;
|
||||
int axis;
|
||||
float threshold;
|
||||
bool trigger_if_greater;
|
||||
};
|
||||
|
||||
class SDLAnalog final : public Input::AnalogDevice {
|
||||
public:
|
||||
SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_)
|
||||
: joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_) {}
|
||||
|
||||
std::tuple<float, float> GetStatus() const override {
|
||||
return joystick->GetAnalog(axis_x, axis_y);
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<SDLJoystick> joystick;
|
||||
int axis_x;
|
||||
int axis_y;
|
||||
};
|
||||
|
||||
/// A button device factory that creates button devices from SDL joystick
|
||||
class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> {
|
||||
public:
|
||||
/**
|
||||
* Creates a button device from a joystick button
|
||||
* @param params contains parameters for creating the device:
|
||||
* - "guid": the guid of the joystick to bind
|
||||
* - "port": the nth joystick of the same type to bind
|
||||
* - "button"(optional): the index of the button to bind
|
||||
* - "hat"(optional): the index of the hat to bind as direction buttons
|
||||
* - "axis"(optional): the index of the axis to bind
|
||||
* - "direction"(only used for hat): the direction name of the hat to bind. Can be "up",
|
||||
* "down", "left" or "right"
|
||||
* - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
|
||||
* triggered if the axis value crosses
|
||||
* - "direction"(only used for axis): "+" means the button is triggered when the axis
|
||||
* value is greater than the threshold; "-" means the button is triggered when the axis
|
||||
* value is smaller than the threshold
|
||||
*/
|
||||
std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override {
|
||||
const std::string guid = params.Get("guid", "0");
|
||||
const int port = params.Get("port", 0);
|
||||
|
||||
auto joystick = GetSDLJoystickByGUID(guid, port);
|
||||
|
||||
if (params.Has("hat")) {
|
||||
const int hat = params.Get("hat", 0);
|
||||
const std::string direction_name = params.Get("direction", "");
|
||||
Uint8 direction;
|
||||
if (direction_name == "up") {
|
||||
direction = SDL_HAT_UP;
|
||||
} else if (direction_name == "down") {
|
||||
direction = SDL_HAT_DOWN;
|
||||
} else if (direction_name == "left") {
|
||||
direction = SDL_HAT_LEFT;
|
||||
} else if (direction_name == "right") {
|
||||
direction = SDL_HAT_RIGHT;
|
||||
} else {
|
||||
direction = 0;
|
||||
}
|
||||
// This is necessary so accessing GetHat with hat won't crash
|
||||
joystick->SetHat(hat, SDL_HAT_CENTERED);
|
||||
return std::make_unique<SDLDirectionButton>(joystick, hat, direction);
|
||||
}
|
||||
|
||||
if (params.Has("axis")) {
|
||||
const int axis = params.Get("axis", 0);
|
||||
const float threshold = params.Get("threshold", 0.5f);
|
||||
const std::string direction_name = params.Get("direction", "");
|
||||
bool trigger_if_greater;
|
||||
if (direction_name == "+") {
|
||||
trigger_if_greater = true;
|
||||
} else if (direction_name == "-") {
|
||||
trigger_if_greater = false;
|
||||
} else {
|
||||
trigger_if_greater = true;
|
||||
LOG_ERROR(Input, "Unknown direction '{}'", direction_name);
|
||||
}
|
||||
// This is necessary so accessing GetAxis with axis won't crash
|
||||
joystick->SetAxis(axis, 0);
|
||||
return std::make_unique<SDLAxisButton>(joystick, axis, threshold, trigger_if_greater);
|
||||
}
|
||||
|
||||
const int button = params.Get("button", 0);
|
||||
// This is necessary so accessing GetButton with button won't crash
|
||||
joystick->SetButton(button, false);
|
||||
return std::make_unique<SDLButton>(joystick, button);
|
||||
}
|
||||
};
|
||||
|
||||
/// An analog device factory that creates analog devices from SDL joystick
|
||||
class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
|
||||
public:
|
||||
/**
|
||||
* Creates analog device from joystick axes
|
||||
* @param params contains parameters for creating the device:
|
||||
* - "guid": the guid of the joystick to bind
|
||||
* - "port": the nth joystick of the same type
|
||||
* - "axis_x": the index of the axis to be bind as x-axis
|
||||
* - "axis_y": the index of the axis to be bind as y-axis
|
||||
*/
|
||||
std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override {
|
||||
const std::string guid = params.Get("guid", "0");
|
||||
const int port = params.Get("port", 0);
|
||||
const int axis_x = params.Get("axis_x", 0);
|
||||
const int axis_y = params.Get("axis_y", 1);
|
||||
|
||||
auto joystick = GetSDLJoystickByGUID(guid, port);
|
||||
|
||||
// This is necessary so accessing GetAxis with axis_x and axis_y won't crash
|
||||
joystick->SetAxis(axis_x, 0);
|
||||
joystick->SetAxis(axis_y, 0);
|
||||
return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y);
|
||||
}
|
||||
};
|
||||
|
||||
void Init() {
|
||||
using namespace Input;
|
||||
RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>());
|
||||
RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>());
|
||||
polling = false;
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
if (initialized) {
|
||||
using namespace Input;
|
||||
UnregisterFactory<ButtonDevice>("sdl");
|
||||
UnregisterFactory<AnalogDevice>("sdl");
|
||||
initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event) {
|
||||
Common::ParamPackage params({{"engine", "sdl"}});
|
||||
switch (event.type) {
|
||||
case SDL_JOYAXISMOTION: {
|
||||
auto joystick = GetSDLJoystickBySDLID(event.jaxis.which);
|
||||
params.Set("port", joystick->GetPort());
|
||||
params.Set("guid", joystick->GetGUID());
|
||||
params.Set("axis", event.jaxis.axis);
|
||||
if (event.jaxis.value > 0) {
|
||||
params.Set("direction", "+");
|
||||
params.Set("threshold", "0.5");
|
||||
} else {
|
||||
params.Set("direction", "-");
|
||||
params.Set("threshold", "-0.5");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_JOYBUTTONUP: {
|
||||
auto joystick = GetSDLJoystickBySDLID(event.jbutton.which);
|
||||
params.Set("port", joystick->GetPort());
|
||||
params.Set("guid", joystick->GetGUID());
|
||||
params.Set("button", event.jbutton.button);
|
||||
break;
|
||||
}
|
||||
case SDL_JOYHATMOTION: {
|
||||
auto joystick = GetSDLJoystickBySDLID(event.jhat.which);
|
||||
params.Set("port", joystick->GetPort());
|
||||
params.Set("guid", joystick->GetGUID());
|
||||
params.Set("hat", event.jhat.hat);
|
||||
switch (event.jhat.value) {
|
||||
case SDL_HAT_UP:
|
||||
params.Set("direction", "up");
|
||||
break;
|
||||
case SDL_HAT_DOWN:
|
||||
params.Set("direction", "down");
|
||||
break;
|
||||
case SDL_HAT_LEFT:
|
||||
params.Set("direction", "left");
|
||||
break;
|
||||
case SDL_HAT_RIGHT:
|
||||
params.Set("direction", "right");
|
||||
break;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
namespace Polling {
|
||||
|
||||
class SDLPoller : public InputCommon::Polling::DevicePoller {
|
||||
public:
|
||||
void Start() override {
|
||||
event_queue.Clear();
|
||||
polling = true;
|
||||
}
|
||||
|
||||
void Stop() override {
|
||||
polling = false;
|
||||
}
|
||||
};
|
||||
|
||||
class SDLButtonPoller final : public SDLPoller {
|
||||
public:
|
||||
Common::ParamPackage GetNextInput() override {
|
||||
SDL_Event event;
|
||||
while (event_queue.Pop(event)) {
|
||||
switch (event.type) {
|
||||
case SDL_JOYAXISMOTION:
|
||||
if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
|
||||
break;
|
||||
}
|
||||
case SDL_JOYBUTTONUP:
|
||||
case SDL_JOYHATMOTION:
|
||||
return SDLEventToButtonParamPackage(event);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
class SDLAnalogPoller final : public SDLPoller {
|
||||
public:
|
||||
void Start() override {
|
||||
SDLPoller::Start();
|
||||
|
||||
// Reset stored axes
|
||||
analog_xaxis = -1;
|
||||
analog_yaxis = -1;
|
||||
analog_axes_joystick = -1;
|
||||
}
|
||||
|
||||
Common::ParamPackage GetNextInput() override {
|
||||
SDL_Event event;
|
||||
while (event_queue.Pop(event)) {
|
||||
if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) {
|
||||
continue;
|
||||
}
|
||||
// An analog device needs two axes, so we need to store the axis for later and wait for
|
||||
// a second SDL event. The axes also must be from the same joystick.
|
||||
int axis = event.jaxis.axis;
|
||||
if (analog_xaxis == -1) {
|
||||
analog_xaxis = axis;
|
||||
analog_axes_joystick = event.jaxis.which;
|
||||
} else if (analog_yaxis == -1 && analog_xaxis != axis &&
|
||||
analog_axes_joystick == event.jaxis.which) {
|
||||
analog_yaxis = axis;
|
||||
}
|
||||
}
|
||||
Common::ParamPackage params;
|
||||
if (analog_xaxis != -1 && analog_yaxis != -1) {
|
||||
auto joystick = GetSDLJoystickBySDLID(event.jaxis.which);
|
||||
params.Set("engine", "sdl");
|
||||
params.Set("port", joystick->GetPort());
|
||||
params.Set("guid", joystick->GetGUID());
|
||||
params.Set("axis_x", analog_xaxis);
|
||||
params.Set("axis_y", analog_yaxis);
|
||||
analog_xaxis = -1;
|
||||
analog_yaxis = -1;
|
||||
analog_axes_joystick = -1;
|
||||
return params;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
private:
|
||||
int analog_xaxis = -1;
|
||||
int analog_yaxis = -1;
|
||||
SDL_JoystickID analog_axes_joystick = -1;
|
||||
};
|
||||
|
||||
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
|
||||
InputCommon::Polling::DeviceType type) {
|
||||
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> pollers;
|
||||
switch (type) {
|
||||
case InputCommon::Polling::DeviceType::Analog:
|
||||
pollers.push_back(std::make_unique<SDLAnalogPoller>());
|
||||
break;
|
||||
case InputCommon::Polling::DeviceType::Button:
|
||||
pollers.push_back(std::make_unique<SDLButtonPoller>());
|
||||
break;
|
||||
}
|
||||
return pollers;
|
||||
}
|
||||
} // namespace Polling
|
||||
} // namespace SDL
|
||||
} // namespace InputCommon
|
||||
} // namespace InputCommon::SDL
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@@ -7,45 +7,36 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "core/frontend/input.h"
|
||||
#include "input_common/main.h"
|
||||
|
||||
union SDL_Event;
|
||||
|
||||
namespace Common {
|
||||
class ParamPackage;
|
||||
}
|
||||
namespace InputCommon {
|
||||
namespace Polling {
|
||||
} // namespace Common
|
||||
|
||||
namespace InputCommon::Polling {
|
||||
class DevicePoller;
|
||||
enum class DeviceType;
|
||||
} // namespace Polling
|
||||
} // namespace InputCommon
|
||||
} // namespace InputCommon::Polling
|
||||
|
||||
namespace InputCommon {
|
||||
namespace SDL {
|
||||
namespace InputCommon::SDL {
|
||||
|
||||
/// Initializes and registers SDL device factories
|
||||
void Init();
|
||||
class State {
|
||||
public:
|
||||
/// Unresisters SDL device factories and shut them down.
|
||||
virtual ~State() = default;
|
||||
|
||||
/// Unresisters SDL device factories and shut them down.
|
||||
void Shutdown();
|
||||
virtual std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
|
||||
InputCommon::Polling::DeviceType type) = 0;
|
||||
};
|
||||
|
||||
/// Needs to be called before SDL_QuitSubSystem.
|
||||
void CloseSDLJoysticks();
|
||||
class NullState : public State {
|
||||
public:
|
||||
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
|
||||
InputCommon::Polling::DeviceType type) override {}
|
||||
};
|
||||
|
||||
/// Handle SDL_Events for joysticks from SDL_PollEvent
|
||||
void HandleGameControllerEvent(const SDL_Event& event);
|
||||
std::unique_ptr<State> Init();
|
||||
|
||||
/// A Loop that calls HandleGameControllerEvent until Shutdown is called
|
||||
void PollLoop();
|
||||
|
||||
/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice
|
||||
Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event);
|
||||
|
||||
namespace Polling {
|
||||
|
||||
/// Get all DevicePoller that use the SDL backend for a specific device type
|
||||
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
|
||||
InputCommon::Polling::DeviceType type);
|
||||
|
||||
} // namespace Polling
|
||||
} // namespace SDL
|
||||
} // namespace InputCommon
|
||||
} // namespace InputCommon::SDL
|
||||
|
||||
668
src/input_common/sdl/sdl_impl.cpp
Normal file
668
src/input_common/sdl/sdl_impl.cpp
Normal file
@@ -0,0 +1,668 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <SDL.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/math_util.h"
|
||||
#include "common/param_package.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
#include "core/frontend/input.h"
|
||||
#include "input_common/sdl/sdl_impl.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
namespace SDL {
|
||||
|
||||
static std::string GetGUID(SDL_Joystick* joystick) {
|
||||
SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
|
||||
char guid_str[33];
|
||||
SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
|
||||
return guid_str;
|
||||
}
|
||||
|
||||
/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice
|
||||
static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event);
|
||||
|
||||
static int SDLEventWatcher(void* userdata, SDL_Event* event) {
|
||||
SDLState* sdl_state = reinterpret_cast<SDLState*>(userdata);
|
||||
// Don't handle the event if we are configuring
|
||||
if (sdl_state->polling) {
|
||||
sdl_state->event_queue.Push(*event);
|
||||
} else {
|
||||
sdl_state->HandleGameControllerEvent(*event);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
class SDLJoystick {
|
||||
public:
|
||||
SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
|
||||
decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose)
|
||||
: guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, deleter} {}
|
||||
|
||||
void SetButton(int button, bool value) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
state.buttons[button] = value;
|
||||
}
|
||||
|
||||
bool GetButton(int button) const {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
return state.buttons.at(button);
|
||||
}
|
||||
|
||||
void SetAxis(int axis, Sint16 value) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
state.axes[axis] = value;
|
||||
}
|
||||
|
||||
float GetAxis(int axis) const {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
return state.axes.at(axis) / 32767.0f;
|
||||
}
|
||||
|
||||
std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const {
|
||||
float x = GetAxis(axis_x);
|
||||
float y = GetAxis(axis_y);
|
||||
y = -y; // 3DS uses an y-axis inverse from SDL
|
||||
|
||||
// Make sure the coordinates are in the unit circle,
|
||||
// otherwise normalize it.
|
||||
float r = x * x + y * y;
|
||||
if (r > 1.0f) {
|
||||
r = std::sqrt(r);
|
||||
x /= r;
|
||||
y /= r;
|
||||
}
|
||||
|
||||
return std::make_tuple(x, y);
|
||||
}
|
||||
|
||||
void SetHat(int hat, Uint8 direction) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
state.hats[hat] = direction;
|
||||
}
|
||||
|
||||
bool GetHatDirection(int hat, Uint8 direction) const {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
return (state.hats.at(hat) & direction) != 0;
|
||||
}
|
||||
/**
|
||||
* The guid of the joystick
|
||||
*/
|
||||
const std::string& GetGUID() const {
|
||||
return guid;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of joystick from the same type that were connected before this joystick
|
||||
*/
|
||||
int GetPort() const {
|
||||
return port;
|
||||
}
|
||||
|
||||
SDL_Joystick* GetSDLJoystick() const {
|
||||
return sdl_joystick.get();
|
||||
}
|
||||
|
||||
void SetSDLJoystick(SDL_Joystick* joystick,
|
||||
decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) {
|
||||
sdl_joystick =
|
||||
std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)>(joystick, deleter);
|
||||
}
|
||||
|
||||
private:
|
||||
struct State {
|
||||
std::unordered_map<int, bool> buttons;
|
||||
std::unordered_map<int, Sint16> axes;
|
||||
std::unordered_map<int, Uint8> hats;
|
||||
} state;
|
||||
std::string guid;
|
||||
int port;
|
||||
std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
|
||||
mutable std::mutex mutex;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the nth joystick with the corresponding GUID
|
||||
*/
|
||||
std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) {
|
||||
std::lock_guard<std::mutex> lock(joystick_map_mutex);
|
||||
const auto it = joystick_map.find(guid);
|
||||
if (it != joystick_map.end()) {
|
||||
while (it->second.size() <= port) {
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, it->second.size(), nullptr,
|
||||
[](SDL_Joystick*) {});
|
||||
it->second.emplace_back(std::move(joystick));
|
||||
}
|
||||
return it->second[port];
|
||||
}
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, [](SDL_Joystick*) {});
|
||||
return joystick_map[guid].emplace_back(std::move(joystick));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check how many identical joysticks (by guid) were connected before the one with sdl_id and so tie
|
||||
* it to a SDLJoystick with the same guid and that port
|
||||
*/
|
||||
std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
|
||||
auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
|
||||
const std::string guid = GetGUID(sdl_joystick);
|
||||
std::lock_guard<std::mutex> lock(joystick_map_mutex);
|
||||
auto map_it = joystick_map.find(guid);
|
||||
if (map_it != joystick_map.end()) {
|
||||
auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(),
|
||||
[&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) {
|
||||
return sdl_joystick == joystick->GetSDLJoystick();
|
||||
});
|
||||
if (vec_it != map_it->second.end()) {
|
||||
// This is the common case: There is already an existing SDL_Joystick maped to a
|
||||
// SDLJoystick. return the SDLJoystick
|
||||
return *vec_it;
|
||||
}
|
||||
// Search for a SDLJoystick without a mapped SDL_Joystick...
|
||||
auto nullptr_it = std::find_if(map_it->second.begin(), map_it->second.end(),
|
||||
[](const std::shared_ptr<SDLJoystick>& joystick) {
|
||||
return !joystick->GetSDLJoystick();
|
||||
});
|
||||
if (nullptr_it != map_it->second.end()) {
|
||||
// ... and map it
|
||||
(*nullptr_it)->SetSDLJoystick(sdl_joystick);
|
||||
return *nullptr_it;
|
||||
}
|
||||
// There is no SDLJoystick without a mapped SDL_Joystick
|
||||
// Create a new SDLJoystick
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, map_it->second.size(), sdl_joystick);
|
||||
return map_it->second.emplace_back(std::move(joystick));
|
||||
}
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
|
||||
return joystick_map[guid].emplace_back(std::move(joystick));
|
||||
}
|
||||
|
||||
void SDLState::InitJoystick(int joystick_index) {
|
||||
SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
|
||||
if (!sdl_joystick) {
|
||||
LOG_ERROR(Input, "failed to open joystick {}", joystick_index);
|
||||
return;
|
||||
}
|
||||
std::string guid = GetGUID(sdl_joystick);
|
||||
std::lock_guard<std::mutex> lock(joystick_map_mutex);
|
||||
if (joystick_map.find(guid) == joystick_map.end()) {
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
|
||||
joystick_map[guid].emplace_back(std::move(joystick));
|
||||
return;
|
||||
}
|
||||
auto& joystick_guid_list = joystick_map[guid];
|
||||
const auto it = std::find_if(
|
||||
joystick_guid_list.begin(), joystick_guid_list.end(),
|
||||
[](const std::shared_ptr<SDLJoystick>& joystick) { return !joystick->GetSDLJoystick(); });
|
||||
if (it != joystick_guid_list.end()) {
|
||||
(*it)->SetSDLJoystick(sdl_joystick);
|
||||
return;
|
||||
}
|
||||
auto joystick = std::make_shared<SDLJoystick>(guid, joystick_guid_list.size(), sdl_joystick);
|
||||
joystick_guid_list.emplace_back(std::move(joystick));
|
||||
}
|
||||
|
||||
void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) {
|
||||
std::string guid = GetGUID(sdl_joystick);
|
||||
std::shared_ptr<SDLJoystick> joystick;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(joystick_map_mutex);
|
||||
// This call to guid is safe since the joystick is guaranteed to be in the map
|
||||
auto& joystick_guid_list = joystick_map[guid];
|
||||
const auto joystick_it =
|
||||
std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
|
||||
[&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) {
|
||||
return joystick->GetSDLJoystick() == sdl_joystick;
|
||||
});
|
||||
joystick = *joystick_it;
|
||||
}
|
||||
// Destruct SDL_Joystick outside the lock guard because SDL can internally call event calback
|
||||
// which locks the mutex again
|
||||
joystick->SetSDLJoystick(nullptr, [](SDL_Joystick*) {});
|
||||
}
|
||||
|
||||
void SDLState::HandleGameControllerEvent(const SDL_Event& event) {
|
||||
switch (event.type) {
|
||||
case SDL_JOYBUTTONUP: {
|
||||
if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
|
||||
joystick->SetButton(event.jbutton.button, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_JOYBUTTONDOWN: {
|
||||
if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
|
||||
joystick->SetButton(event.jbutton.button, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_JOYHATMOTION: {
|
||||
if (auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) {
|
||||
joystick->SetHat(event.jhat.hat, event.jhat.value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_JOYAXISMOTION: {
|
||||
if (auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) {
|
||||
joystick->SetAxis(event.jaxis.axis, event.jaxis.value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_JOYDEVICEREMOVED:
|
||||
LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
|
||||
CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
|
||||
break;
|
||||
case SDL_JOYDEVICEADDED:
|
||||
LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which);
|
||||
InitJoystick(event.jdevice.which);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SDLState::CloseJoysticks() {
|
||||
std::lock_guard<std::mutex> lock(joystick_map_mutex);
|
||||
joystick_map.clear();
|
||||
}
|
||||
|
||||
class SDLButton final : public Input::ButtonDevice {
|
||||
public:
|
||||
explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_)
|
||||
: joystick(std::move(joystick_)), button(button_) {}
|
||||
|
||||
bool GetStatus() const override {
|
||||
return joystick->GetButton(button);
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<SDLJoystick> joystick;
|
||||
int button;
|
||||
};
|
||||
|
||||
class SDLDirectionButton final : public Input::ButtonDevice {
|
||||
public:
|
||||
explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
|
||||
: joystick(std::move(joystick_)), hat(hat_), direction(direction_) {}
|
||||
|
||||
bool GetStatus() const override {
|
||||
return joystick->GetHatDirection(hat, direction);
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<SDLJoystick> joystick;
|
||||
int hat;
|
||||
Uint8 direction;
|
||||
};
|
||||
|
||||
class SDLAxisButton final : public Input::ButtonDevice {
|
||||
public:
|
||||
explicit SDLAxisButton(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_,
|
||||
bool trigger_if_greater_)
|
||||
: joystick(std::move(joystick_)), axis(axis_), threshold(threshold_),
|
||||
trigger_if_greater(trigger_if_greater_) {}
|
||||
|
||||
bool GetStatus() const override {
|
||||
float axis_value = joystick->GetAxis(axis);
|
||||
if (trigger_if_greater)
|
||||
return axis_value > threshold;
|
||||
return axis_value < threshold;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<SDLJoystick> joystick;
|
||||
int axis;
|
||||
float threshold;
|
||||
bool trigger_if_greater;
|
||||
};
|
||||
|
||||
class SDLAnalog final : public Input::AnalogDevice {
|
||||
public:
|
||||
SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, float deadzone_)
|
||||
: joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_) {}
|
||||
|
||||
std::tuple<float, float> GetStatus() const override {
|
||||
const auto [x, y] = joystick->GetAnalog(axis_x, axis_y);
|
||||
const float r = std::sqrt((x * x) + (y * y));
|
||||
if (r > deadzone) {
|
||||
return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone),
|
||||
y / r * (r - deadzone) / (1 - deadzone));
|
||||
}
|
||||
return std::make_tuple<float, float>(0.0f, 0.0f);
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<SDLJoystick> joystick;
|
||||
const int axis_x;
|
||||
const int axis_y;
|
||||
const float deadzone;
|
||||
};
|
||||
|
||||
/// A button device factory that creates button devices from SDL joystick
|
||||
class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> {
|
||||
public:
|
||||
explicit SDLButtonFactory(SDLState& state_) : state(state_) {}
|
||||
|
||||
/**
|
||||
* Creates a button device from a joystick button
|
||||
* @param params contains parameters for creating the device:
|
||||
* - "guid": the guid of the joystick to bind
|
||||
* - "port": the nth joystick of the same type to bind
|
||||
* - "button"(optional): the index of the button to bind
|
||||
* - "hat"(optional): the index of the hat to bind as direction buttons
|
||||
* - "axis"(optional): the index of the axis to bind
|
||||
* - "direction"(only used for hat): the direction name of the hat to bind. Can be "up",
|
||||
* "down", "left" or "right"
|
||||
* - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
|
||||
* triggered if the axis value crosses
|
||||
* - "direction"(only used for axis): "+" means the button is triggered when the axis
|
||||
* value is greater than the threshold; "-" means the button is triggered when the axis
|
||||
* value is smaller than the threshold
|
||||
*/
|
||||
std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override {
|
||||
const std::string guid = params.Get("guid", "0");
|
||||
const int port = params.Get("port", 0);
|
||||
|
||||
auto joystick = state.GetSDLJoystickByGUID(guid, port);
|
||||
|
||||
if (params.Has("hat")) {
|
||||
const int hat = params.Get("hat", 0);
|
||||
const std::string direction_name = params.Get("direction", "");
|
||||
Uint8 direction;
|
||||
if (direction_name == "up") {
|
||||
direction = SDL_HAT_UP;
|
||||
} else if (direction_name == "down") {
|
||||
direction = SDL_HAT_DOWN;
|
||||
} else if (direction_name == "left") {
|
||||
direction = SDL_HAT_LEFT;
|
||||
} else if (direction_name == "right") {
|
||||
direction = SDL_HAT_RIGHT;
|
||||
} else {
|
||||
direction = 0;
|
||||
}
|
||||
// This is necessary so accessing GetHat with hat won't crash
|
||||
joystick->SetHat(hat, SDL_HAT_CENTERED);
|
||||
return std::make_unique<SDLDirectionButton>(joystick, hat, direction);
|
||||
}
|
||||
|
||||
if (params.Has("axis")) {
|
||||
const int axis = params.Get("axis", 0);
|
||||
const float threshold = params.Get("threshold", 0.5f);
|
||||
const std::string direction_name = params.Get("direction", "");
|
||||
bool trigger_if_greater;
|
||||
if (direction_name == "+") {
|
||||
trigger_if_greater = true;
|
||||
} else if (direction_name == "-") {
|
||||
trigger_if_greater = false;
|
||||
} else {
|
||||
trigger_if_greater = true;
|
||||
LOG_ERROR(Input, "Unknown direction {}", direction_name);
|
||||
}
|
||||
// This is necessary so accessing GetAxis with axis won't crash
|
||||
joystick->SetAxis(axis, 0);
|
||||
return std::make_unique<SDLAxisButton>(joystick, axis, threshold, trigger_if_greater);
|
||||
}
|
||||
|
||||
const int button = params.Get("button", 0);
|
||||
// This is necessary so accessing GetButton with button won't crash
|
||||
joystick->SetButton(button, false);
|
||||
return std::make_unique<SDLButton>(joystick, button);
|
||||
}
|
||||
|
||||
private:
|
||||
SDLState& state;
|
||||
};
|
||||
|
||||
/// An analog device factory that creates analog devices from SDL joystick
|
||||
class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
|
||||
public:
|
||||
explicit SDLAnalogFactory(SDLState& state_) : state(state_) {}
|
||||
/**
|
||||
* Creates analog device from joystick axes
|
||||
* @param params contains parameters for creating the device:
|
||||
* - "guid": the guid of the joystick to bind
|
||||
* - "port": the nth joystick of the same type
|
||||
* - "axis_x": the index of the axis to be bind as x-axis
|
||||
* - "axis_y": the index of the axis to be bind as y-axis
|
||||
*/
|
||||
std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override {
|
||||
const std::string guid = params.Get("guid", "0");
|
||||
const int port = params.Get("port", 0);
|
||||
const int axis_x = params.Get("axis_x", 0);
|
||||
const int axis_y = params.Get("axis_y", 1);
|
||||
float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f);
|
||||
|
||||
auto joystick = state.GetSDLJoystickByGUID(guid, port);
|
||||
|
||||
// This is necessary so accessing GetAxis with axis_x and axis_y won't crash
|
||||
joystick->SetAxis(axis_x, 0);
|
||||
joystick->SetAxis(axis_y, 0);
|
||||
return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone);
|
||||
}
|
||||
|
||||
private:
|
||||
SDLState& state;
|
||||
};
|
||||
|
||||
SDLState::SDLState() {
|
||||
using namespace Input;
|
||||
RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>(*this));
|
||||
RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>(*this));
|
||||
|
||||
// If the frontend is going to manage the event loop, then we dont start one here
|
||||
start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK);
|
||||
if (start_thread && SDL_Init(SDL_INIT_JOYSTICK) < 0) {
|
||||
LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) {
|
||||
LOG_ERROR(Input, "Failed to set Hint for background events", SDL_GetError());
|
||||
}
|
||||
|
||||
SDL_AddEventWatch(&SDLEventWatcher, this);
|
||||
|
||||
initialized = true;
|
||||
if (start_thread) {
|
||||
poll_thread = std::thread([this] {
|
||||
using namespace std::chrono_literals;
|
||||
while (initialized) {
|
||||
SDL_PumpEvents();
|
||||
std::this_thread::sleep_for(10ms);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Because the events for joystick connection happens before we have our event watcher added, we
|
||||
// can just open all the joysticks right here
|
||||
for (int i = 0; i < SDL_NumJoysticks(); ++i) {
|
||||
InitJoystick(i);
|
||||
}
|
||||
}
|
||||
|
||||
SDLState::~SDLState() {
|
||||
using namespace Input;
|
||||
UnregisterFactory<ButtonDevice>("sdl");
|
||||
UnregisterFactory<AnalogDevice>("sdl");
|
||||
|
||||
CloseJoysticks();
|
||||
SDL_DelEventWatch(&SDLEventWatcher, this);
|
||||
|
||||
initialized = false;
|
||||
if (start_thread) {
|
||||
poll_thread.join();
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
}
|
||||
}
|
||||
|
||||
Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) {
|
||||
Common::ParamPackage params({{"engine", "sdl"}});
|
||||
|
||||
switch (event.type) {
|
||||
case SDL_JOYAXISMOTION: {
|
||||
auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
|
||||
params.Set("port", joystick->GetPort());
|
||||
params.Set("guid", joystick->GetGUID());
|
||||
params.Set("axis", event.jaxis.axis);
|
||||
if (event.jaxis.value > 0) {
|
||||
params.Set("direction", "+");
|
||||
params.Set("threshold", "0.5");
|
||||
} else {
|
||||
params.Set("direction", "-");
|
||||
params.Set("threshold", "-0.5");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_JOYBUTTONUP: {
|
||||
auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which);
|
||||
params.Set("port", joystick->GetPort());
|
||||
params.Set("guid", joystick->GetGUID());
|
||||
params.Set("button", event.jbutton.button);
|
||||
break;
|
||||
}
|
||||
case SDL_JOYHATMOTION: {
|
||||
auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which);
|
||||
params.Set("port", joystick->GetPort());
|
||||
params.Set("guid", joystick->GetGUID());
|
||||
params.Set("hat", event.jhat.hat);
|
||||
switch (event.jhat.value) {
|
||||
case SDL_HAT_UP:
|
||||
params.Set("direction", "up");
|
||||
break;
|
||||
case SDL_HAT_DOWN:
|
||||
params.Set("direction", "down");
|
||||
break;
|
||||
case SDL_HAT_LEFT:
|
||||
params.Set("direction", "left");
|
||||
break;
|
||||
case SDL_HAT_RIGHT:
|
||||
params.Set("direction", "right");
|
||||
break;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
namespace Polling {
|
||||
|
||||
class SDLPoller : public InputCommon::Polling::DevicePoller {
|
||||
public:
|
||||
explicit SDLPoller(SDLState& state_) : state(state_) {}
|
||||
|
||||
void Start() override {
|
||||
state.event_queue.Clear();
|
||||
state.polling = true;
|
||||
}
|
||||
|
||||
void Stop() override {
|
||||
state.polling = false;
|
||||
}
|
||||
|
||||
protected:
|
||||
SDLState& state;
|
||||
};
|
||||
|
||||
class SDLButtonPoller final : public SDLPoller {
|
||||
public:
|
||||
explicit SDLButtonPoller(SDLState& state_) : SDLPoller(state_) {}
|
||||
|
||||
Common::ParamPackage GetNextInput() override {
|
||||
SDL_Event event;
|
||||
while (state.event_queue.Pop(event)) {
|
||||
switch (event.type) {
|
||||
case SDL_JOYAXISMOTION:
|
||||
if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
|
||||
break;
|
||||
}
|
||||
case SDL_JOYBUTTONUP:
|
||||
case SDL_JOYHATMOTION:
|
||||
return SDLEventToButtonParamPackage(state, event);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
class SDLAnalogPoller final : public SDLPoller {
|
||||
public:
|
||||
explicit SDLAnalogPoller(SDLState& state_) : SDLPoller(state_) {}
|
||||
|
||||
void Start() override {
|
||||
SDLPoller::Start();
|
||||
|
||||
// Reset stored axes
|
||||
analog_xaxis = -1;
|
||||
analog_yaxis = -1;
|
||||
analog_axes_joystick = -1;
|
||||
}
|
||||
|
||||
Common::ParamPackage GetNextInput() override {
|
||||
SDL_Event event;
|
||||
while (state.event_queue.Pop(event)) {
|
||||
if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) {
|
||||
continue;
|
||||
}
|
||||
// An analog device needs two axes, so we need to store the axis for later and wait for
|
||||
// a second SDL event. The axes also must be from the same joystick.
|
||||
int axis = event.jaxis.axis;
|
||||
if (analog_xaxis == -1) {
|
||||
analog_xaxis = axis;
|
||||
analog_axes_joystick = event.jaxis.which;
|
||||
} else if (analog_yaxis == -1 && analog_xaxis != axis &&
|
||||
analog_axes_joystick == event.jaxis.which) {
|
||||
analog_yaxis = axis;
|
||||
}
|
||||
}
|
||||
Common::ParamPackage params;
|
||||
if (analog_xaxis != -1 && analog_yaxis != -1) {
|
||||
auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
|
||||
params.Set("engine", "sdl");
|
||||
params.Set("port", joystick->GetPort());
|
||||
params.Set("guid", joystick->GetGUID());
|
||||
params.Set("axis_x", analog_xaxis);
|
||||
params.Set("axis_y", analog_yaxis);
|
||||
analog_xaxis = -1;
|
||||
analog_yaxis = -1;
|
||||
analog_axes_joystick = -1;
|
||||
return params;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
private:
|
||||
int analog_xaxis = -1;
|
||||
int analog_yaxis = -1;
|
||||
SDL_JoystickID analog_axes_joystick = -1;
|
||||
};
|
||||
} // namespace Polling
|
||||
|
||||
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> SDLState::GetPollers(
|
||||
InputCommon::Polling::DeviceType type) {
|
||||
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> pollers;
|
||||
switch (type) {
|
||||
case InputCommon::Polling::DeviceType::Analog:
|
||||
pollers.emplace_back(std::make_unique<Polling::SDLAnalogPoller>(*this));
|
||||
break;
|
||||
case InputCommon::Polling::DeviceType::Button:
|
||||
pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this));
|
||||
break;
|
||||
return pollers;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace SDL
|
||||
} // namespace InputCommon
|
||||
64
src/input_common/sdl/sdl_impl.h
Normal file
64
src/input_common/sdl/sdl_impl.h
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include "common/threadsafe_queue.h"
|
||||
#include "input_common/sdl/sdl.h"
|
||||
|
||||
union SDL_Event;
|
||||
using SDL_Joystick = struct _SDL_Joystick;
|
||||
using SDL_JoystickID = s32;
|
||||
|
||||
namespace InputCommon::SDL {
|
||||
|
||||
class SDLJoystick;
|
||||
class SDLButtonFactory;
|
||||
class SDLAnalogFactory;
|
||||
|
||||
class SDLState : public State {
|
||||
public:
|
||||
/// Initializes and registers SDL device factories
|
||||
SDLState();
|
||||
|
||||
/// Unresisters SDL device factories and shut them down.
|
||||
~SDLState() override;
|
||||
|
||||
/// Handle SDL_Events for joysticks from SDL_PollEvent
|
||||
void HandleGameControllerEvent(const SDL_Event& event);
|
||||
|
||||
std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id);
|
||||
std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port);
|
||||
|
||||
/// Get all DevicePoller that use the SDL backend for a specific device type
|
||||
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
|
||||
InputCommon::Polling::DeviceType type) override;
|
||||
|
||||
/// Used by the Pollers during config
|
||||
std::atomic<bool> polling = false;
|
||||
Common::SPSCQueue<SDL_Event> event_queue;
|
||||
|
||||
private:
|
||||
void InitJoystick(int joystick_index);
|
||||
void CloseJoystick(SDL_Joystick* sdl_joystick);
|
||||
|
||||
/// Needs to be called before SDL_QuitSubSystem.
|
||||
void CloseJoysticks();
|
||||
|
||||
/// Map of GUID of a list of corresponding virtual Joysticks
|
||||
std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
|
||||
std::mutex joystick_map_mutex;
|
||||
|
||||
std::shared_ptr<SDLButtonFactory> button_factory;
|
||||
std::shared_ptr<SDLAnalogFactory> analog_factory;
|
||||
|
||||
bool start_thread = false;
|
||||
std::atomic<bool> initialized = false;
|
||||
|
||||
std::thread poll_thread;
|
||||
};
|
||||
} // namespace InputCommon::SDL
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "common/page_table.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/memory.h"
|
||||
@@ -22,7 +23,7 @@ TestEnvironment::TestEnvironment(bool mutable_memory_)
|
||||
std::fill(page_table->pointers.begin(), page_table->pointers.end(), nullptr);
|
||||
page_table->special_regions.clear();
|
||||
std::fill(page_table->attributes.begin(), page_table->attributes.end(),
|
||||
Memory::PageType::Unmapped);
|
||||
Common::PageType::Unmapped);
|
||||
|
||||
Memory::MapIoRegion(*page_table, 0x00000000, 0x80000000, test_memory);
|
||||
Memory::MapIoRegion(*page_table, 0x80000000, 0x80000000, test_memory);
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/memory_hook.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/memory_hook.h"
|
||||
|
||||
namespace Memory {
|
||||
namespace Common {
|
||||
struct PageTable;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ public:
|
||||
|
||||
private:
|
||||
friend struct TestMemory;
|
||||
struct TestMemory final : Memory::MemoryHook {
|
||||
struct TestMemory final : Common::MemoryHook {
|
||||
explicit TestMemory(TestEnvironment* env_) : env(env_) {}
|
||||
TestEnvironment* env;
|
||||
|
||||
@@ -86,7 +86,7 @@ private:
|
||||
bool mutable_memory;
|
||||
std::shared_ptr<TestMemory> test_memory;
|
||||
std::vector<WriteRecord> write_records;
|
||||
Memory::PageTable* page_table = nullptr;
|
||||
Common::PageTable* page_table = nullptr;
|
||||
Kernel::KernelCore kernel;
|
||||
};
|
||||
|
||||
|
||||
@@ -123,6 +123,8 @@ if (ENABLE_VULKAN)
|
||||
renderer_vulkan/vk_memory_manager.h
|
||||
renderer_vulkan/vk_resource_manager.cpp
|
||||
renderer_vulkan/vk_resource_manager.h
|
||||
renderer_vulkan/vk_sampler_cache.cpp
|
||||
renderer_vulkan/vk_sampler_cache.h
|
||||
renderer_vulkan/vk_scheduler.cpp
|
||||
renderer_vulkan/vk_scheduler.h
|
||||
renderer_vulkan/vk_stream_buffer.cpp
|
||||
|
||||
@@ -55,12 +55,9 @@ bool DmaPusher::Step() {
|
||||
}
|
||||
|
||||
// Push buffer non-empty, read a word
|
||||
const auto address = gpu.MemoryManager().GpuToCpuAddress(dma_get);
|
||||
ASSERT_MSG(address, "Invalid GPU address");
|
||||
|
||||
command_headers.resize(command_list_header.size);
|
||||
|
||||
Memory::ReadBlock(*address, command_headers.data(), command_list_header.size * sizeof(u32));
|
||||
gpu.MemoryManager().ReadBlock(dma_get, command_headers.data(),
|
||||
command_list_header.size * sizeof(u32));
|
||||
|
||||
for (const CommandHeader& command_header : command_headers) {
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "video_core/engines/kepler_memory.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
namespace Tegra::Engines {
|
||||
|
||||
@@ -40,17 +41,13 @@ void KeplerMemory::ProcessData(u32 data) {
|
||||
ASSERT_MSG(regs.exec.linear, "Non-linear uploads are not supported");
|
||||
ASSERT(regs.dest.x == 0 && regs.dest.y == 0 && regs.dest.z == 0);
|
||||
|
||||
const GPUVAddr address = regs.dest.Address();
|
||||
const auto dest_address =
|
||||
memory_manager.GpuToCpuAddress(address + state.write_offset * sizeof(u32));
|
||||
ASSERT_MSG(dest_address, "Invalid GPU address");
|
||||
|
||||
// We have to invalidate the destination region to evict any outdated surfaces from the cache.
|
||||
// We do this before actually writing the new data because the destination address might contain
|
||||
// a dirty surface that will have to be written back to memory.
|
||||
Core::System::GetInstance().GPU().InvalidateRegion(*dest_address, sizeof(u32));
|
||||
// We do this before actually writing the new data because the destination address might
|
||||
// contain a dirty surface that will have to be written back to memory.
|
||||
const GPUVAddr address{regs.dest.Address() + state.write_offset * sizeof(u32)};
|
||||
rasterizer.InvalidateRegion(ToCacheAddr(memory_manager.GetPointer(address)), sizeof(u32));
|
||||
memory_manager.Write32(address, data);
|
||||
|
||||
Memory::Write32(*dest_address, data);
|
||||
system.GPU().Maxwell3D().dirty_flags.OnMemoryWrite();
|
||||
|
||||
state.write_offset++;
|
||||
|
||||
@@ -270,11 +270,9 @@ void Maxwell3D::ProcessMacroBind(u32 data) {
|
||||
}
|
||||
|
||||
void Maxwell3D::ProcessQueryGet() {
|
||||
GPUVAddr sequence_address = regs.query.QueryAddress();
|
||||
const GPUVAddr sequence_address{regs.query.QueryAddress()};
|
||||
// Since the sequence address is given as a GPU VAddr, we have to convert it to an application
|
||||
// VAddr before writing.
|
||||
const auto address = memory_manager.GpuToCpuAddress(sequence_address);
|
||||
ASSERT_MSG(address, "Invalid GPU address");
|
||||
|
||||
// TODO(Subv): Support the other query units.
|
||||
ASSERT_MSG(regs.query.query_get.unit == Regs::QueryUnit::Crop,
|
||||
@@ -309,7 +307,7 @@ void Maxwell3D::ProcessQueryGet() {
|
||||
// Write the current query sequence to the sequence address.
|
||||
// TODO(Subv): Find out what happens if you use a long query type but mark it as a short
|
||||
// query.
|
||||
Memory::Write32(*address, sequence);
|
||||
memory_manager.Write32(sequence_address, sequence);
|
||||
} else {
|
||||
// Write the 128-bit result structure in long mode. Note: We emulate an infinitely fast
|
||||
// GPU, this command may actually take a while to complete in real hardware due to GPU
|
||||
@@ -318,7 +316,7 @@ void Maxwell3D::ProcessQueryGet() {
|
||||
query_result.value = result;
|
||||
// TODO(Subv): Generate a real GPU timestamp and write it here instead of CoreTiming
|
||||
query_result.timestamp = system.CoreTiming().GetTicks();
|
||||
Memory::WriteBlock(*address, &query_result, sizeof(query_result));
|
||||
memory_manager.WriteBlock(sequence_address, &query_result, sizeof(query_result));
|
||||
}
|
||||
dirty_flags.OnMemoryWrite();
|
||||
break;
|
||||
@@ -393,10 +391,12 @@ void Maxwell3D::ProcessCBData(u32 value) {
|
||||
// Don't allow writing past the end of the buffer.
|
||||
ASSERT(regs.const_buffer.cb_pos + sizeof(u32) <= regs.const_buffer.cb_size);
|
||||
|
||||
const auto address = memory_manager.GpuToCpuAddress(buffer_address + regs.const_buffer.cb_pos);
|
||||
ASSERT_MSG(address, "Invalid GPU address");
|
||||
const GPUVAddr address{buffer_address + regs.const_buffer.cb_pos};
|
||||
|
||||
u8* ptr{memory_manager.GetPointer(address)};
|
||||
rasterizer.InvalidateRegion(ToCacheAddr(ptr), sizeof(u32));
|
||||
memory_manager.Write32(address, value);
|
||||
|
||||
Memory::Write32(*address, value);
|
||||
dirty_flags.OnMemoryWrite();
|
||||
|
||||
// Increment the current buffer position.
|
||||
@@ -404,14 +404,10 @@ void Maxwell3D::ProcessCBData(u32 value) {
|
||||
}
|
||||
|
||||
Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
|
||||
const GPUVAddr tic_base_address = regs.tic.TICAddress();
|
||||
|
||||
const GPUVAddr tic_address_gpu = tic_base_address + tic_index * sizeof(Texture::TICEntry);
|
||||
const auto tic_address_cpu = memory_manager.GpuToCpuAddress(tic_address_gpu);
|
||||
ASSERT_MSG(tic_address_cpu, "Invalid GPU address");
|
||||
const GPUVAddr tic_address_gpu{regs.tic.TICAddress() + tic_index * sizeof(Texture::TICEntry)};
|
||||
|
||||
Texture::TICEntry tic_entry;
|
||||
Memory::ReadBlock(*tic_address_cpu, &tic_entry, sizeof(Texture::TICEntry));
|
||||
memory_manager.ReadBlock(tic_address_gpu, &tic_entry, sizeof(Texture::TICEntry));
|
||||
|
||||
ASSERT_MSG(tic_entry.header_version == Texture::TICHeaderVersion::BlockLinear ||
|
||||
tic_entry.header_version == Texture::TICHeaderVersion::Pitch,
|
||||
@@ -429,14 +425,10 @@ Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
|
||||
}
|
||||
|
||||
Texture::TSCEntry Maxwell3D::GetTSCEntry(u32 tsc_index) const {
|
||||
const GPUVAddr tsc_base_address = regs.tsc.TSCAddress();
|
||||
|
||||
const GPUVAddr tsc_address_gpu = tsc_base_address + tsc_index * sizeof(Texture::TSCEntry);
|
||||
const auto tsc_address_cpu = memory_manager.GpuToCpuAddress(tsc_address_gpu);
|
||||
ASSERT_MSG(tsc_address_cpu, "Invalid GPU address");
|
||||
const GPUVAddr tsc_address_gpu{regs.tsc.TSCAddress() + tsc_index * sizeof(Texture::TSCEntry)};
|
||||
|
||||
Texture::TSCEntry tsc_entry;
|
||||
Memory::ReadBlock(*tsc_address_cpu, &tsc_entry, sizeof(Texture::TSCEntry));
|
||||
memory_manager.ReadBlock(tsc_address_gpu, &tsc_entry, sizeof(Texture::TSCEntry));
|
||||
return tsc_entry;
|
||||
}
|
||||
|
||||
@@ -455,10 +447,7 @@ std::vector<Texture::FullTextureInfo> Maxwell3D::GetStageTextures(Regs::ShaderSt
|
||||
for (GPUVAddr current_texture = tex_info_buffer.address + TextureInfoOffset;
|
||||
current_texture < tex_info_buffer_end; current_texture += sizeof(Texture::TextureHandle)) {
|
||||
|
||||
const auto address = memory_manager.GpuToCpuAddress(current_texture);
|
||||
ASSERT_MSG(address, "Invalid GPU address");
|
||||
|
||||
const Texture::TextureHandle tex_handle{Memory::Read32(*address)};
|
||||
const Texture::TextureHandle tex_handle{memory_manager.Read32(current_texture)};
|
||||
|
||||
Texture::FullTextureInfo tex_info{};
|
||||
// TODO(Subv): Use the shader to determine which textures are actually accessed.
|
||||
@@ -493,10 +482,7 @@ Texture::FullTextureInfo Maxwell3D::GetStageTexture(Regs::ShaderStage stage,
|
||||
|
||||
ASSERT(tex_info_address < tex_info_buffer.address + tex_info_buffer.size);
|
||||
|
||||
const auto tex_address_cpu = memory_manager.GpuToCpuAddress(tex_info_address);
|
||||
ASSERT_MSG(tex_address_cpu, "Invalid GPU address");
|
||||
|
||||
const Texture::TextureHandle tex_handle{Memory::Read32(*tex_address_cpu)};
|
||||
const Texture::TextureHandle tex_handle{memory_manager.Read32(tex_info_address)};
|
||||
|
||||
Texture::FullTextureInfo tex_info{};
|
||||
tex_info.index = static_cast<u32>(offset);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/engines/maxwell_dma.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
|
||||
namespace Tegra::Engines {
|
||||
@@ -42,11 +43,6 @@ void MaxwellDMA::HandleCopy() {
|
||||
const GPUVAddr source = regs.src_address.Address();
|
||||
const GPUVAddr dest = regs.dst_address.Address();
|
||||
|
||||
const auto source_cpu = memory_manager.GpuToCpuAddress(source);
|
||||
const auto dest_cpu = memory_manager.GpuToCpuAddress(dest);
|
||||
ASSERT_MSG(source_cpu, "Invalid source GPU address");
|
||||
ASSERT_MSG(dest_cpu, "Invalid destination GPU address");
|
||||
|
||||
// TODO(Subv): Perform more research and implement all features of this engine.
|
||||
ASSERT(regs.exec.enable_swizzle == 0);
|
||||
ASSERT(regs.exec.query_mode == Regs::QueryMode::None);
|
||||
@@ -69,7 +65,7 @@ void MaxwellDMA::HandleCopy() {
|
||||
// buffer of length `x_count`, otherwise we copy a 2D image of dimensions (x_count,
|
||||
// y_count).
|
||||
if (!regs.exec.enable_2d) {
|
||||
Memory::CopyBlock(*dest_cpu, *source_cpu, regs.x_count);
|
||||
memory_manager.CopyBlock(dest, source, regs.x_count);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -78,9 +74,9 @@ void MaxwellDMA::HandleCopy() {
|
||||
// rectangle. There is no need to manually flush/invalidate the regions because
|
||||
// CopyBlock does that for us.
|
||||
for (u32 line = 0; line < regs.y_count; ++line) {
|
||||
const VAddr source_line = *source_cpu + line * regs.src_pitch;
|
||||
const VAddr dest_line = *dest_cpu + line * regs.dst_pitch;
|
||||
Memory::CopyBlock(dest_line, source_line, regs.x_count);
|
||||
const GPUVAddr source_line = source + line * regs.src_pitch;
|
||||
const GPUVAddr dest_line = dest + line * regs.dst_pitch;
|
||||
memory_manager.CopyBlock(dest_line, source_line, regs.x_count);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -89,15 +85,18 @@ void MaxwellDMA::HandleCopy() {
|
||||
|
||||
const std::size_t copy_size = regs.x_count * regs.y_count;
|
||||
|
||||
auto source_ptr{memory_manager.GetPointer(source)};
|
||||
auto dst_ptr{memory_manager.GetPointer(dest)};
|
||||
|
||||
const auto FlushAndInvalidate = [&](u32 src_size, u64 dst_size) {
|
||||
// TODO(Subv): For now, manually flush the regions until we implement GPU-accelerated
|
||||
// copying.
|
||||
Core::System::GetInstance().GPU().FlushRegion(*source_cpu, src_size);
|
||||
rasterizer.FlushRegion(ToCacheAddr(source_ptr), src_size);
|
||||
|
||||
// We have to invalidate the destination region to evict any outdated surfaces from the
|
||||
// cache. We do this before actually writing the new data because the destination address
|
||||
// might contain a dirty surface that will have to be written back to memory.
|
||||
Core::System::GetInstance().GPU().InvalidateRegion(*dest_cpu, dst_size);
|
||||
rasterizer.InvalidateRegion(ToCacheAddr(dst_ptr), dst_size);
|
||||
};
|
||||
|
||||
if (regs.exec.is_dst_linear && !regs.exec.is_src_linear) {
|
||||
@@ -110,8 +109,8 @@ void MaxwellDMA::HandleCopy() {
|
||||
copy_size * src_bytes_per_pixel);
|
||||
|
||||
Texture::UnswizzleSubrect(regs.x_count, regs.y_count, regs.dst_pitch,
|
||||
regs.src_params.size_x, src_bytes_per_pixel, *source_cpu,
|
||||
*dest_cpu, regs.src_params.BlockHeight(), regs.src_params.pos_x,
|
||||
regs.src_params.size_x, src_bytes_per_pixel, source_ptr, dst_ptr,
|
||||
regs.src_params.BlockHeight(), regs.src_params.pos_x,
|
||||
regs.src_params.pos_y);
|
||||
} else {
|
||||
ASSERT(regs.dst_params.size_z == 1);
|
||||
@@ -124,7 +123,7 @@ void MaxwellDMA::HandleCopy() {
|
||||
|
||||
// If the input is linear and the output is tiled, swizzle the input and copy it over.
|
||||
Texture::SwizzleSubrect(regs.x_count, regs.y_count, regs.src_pitch, regs.dst_params.size_x,
|
||||
src_bpp, *dest_cpu, *source_cpu, regs.dst_params.BlockHeight());
|
||||
src_bpp, dst_ptr, source_ptr, regs.dst_params.BlockHeight());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -274,7 +274,6 @@ void GPU::ProcessSemaphoreTriggerMethod() {
|
||||
const auto op =
|
||||
static_cast<GpuSemaphoreOperation>(regs.semaphore_trigger & semaphoreOperationMask);
|
||||
if (op == GpuSemaphoreOperation::WriteLong) {
|
||||
auto address = memory_manager->GpuToCpuAddress(regs.smaphore_address.SmaphoreAddress());
|
||||
struct Block {
|
||||
u32 sequence;
|
||||
u32 zeros = 0;
|
||||
@@ -286,11 +285,9 @@ void GPU::ProcessSemaphoreTriggerMethod() {
|
||||
// TODO(Kmather73): Generate a real GPU timestamp and write it here instead of
|
||||
// CoreTiming
|
||||
block.timestamp = Core::System::GetInstance().CoreTiming().GetTicks();
|
||||
Memory::WriteBlock(*address, &block, sizeof(block));
|
||||
memory_manager->WriteBlock(regs.smaphore_address.SmaphoreAddress(), &block, sizeof(block));
|
||||
} else {
|
||||
const auto address =
|
||||
memory_manager->GpuToCpuAddress(regs.smaphore_address.SmaphoreAddress());
|
||||
const u32 word = Memory::Read32(*address);
|
||||
const u32 word{memory_manager->Read32(regs.smaphore_address.SmaphoreAddress())};
|
||||
if ((op == GpuSemaphoreOperation::AcquireEqual && word == regs.semaphore_sequence) ||
|
||||
(op == GpuSemaphoreOperation::AcquireGequal &&
|
||||
static_cast<s32>(word - regs.semaphore_sequence) > 0) ||
|
||||
@@ -317,13 +314,11 @@ void GPU::ProcessSemaphoreTriggerMethod() {
|
||||
}
|
||||
|
||||
void GPU::ProcessSemaphoreRelease() {
|
||||
const auto address = memory_manager->GpuToCpuAddress(regs.smaphore_address.SmaphoreAddress());
|
||||
Memory::Write32(*address, regs.semaphore_release);
|
||||
memory_manager->Write32(regs.smaphore_address.SmaphoreAddress(), regs.semaphore_release);
|
||||
}
|
||||
|
||||
void GPU::ProcessSemaphoreAcquire() {
|
||||
const auto address = memory_manager->GpuToCpuAddress(regs.smaphore_address.SmaphoreAddress());
|
||||
const u32 word = Memory::Read32(*address);
|
||||
const u32 word = memory_manager->Read32(regs.smaphore_address.SmaphoreAddress());
|
||||
const auto value = regs.semaphore_acquire;
|
||||
if (word != value) {
|
||||
regs.acquire_active = true;
|
||||
|
||||
@@ -11,6 +11,11 @@
|
||||
#include "video_core/dma_pusher.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
|
||||
using CacheAddr = std::uintptr_t;
|
||||
inline CacheAddr ToCacheAddr(const void* host_ptr) {
|
||||
return reinterpret_cast<CacheAddr>(host_ptr);
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
@@ -209,13 +214,13 @@ public:
|
||||
std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) = 0;
|
||||
|
||||
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
|
||||
virtual void FlushRegion(VAddr addr, u64 size) = 0;
|
||||
virtual void FlushRegion(CacheAddr addr, u64 size) = 0;
|
||||
|
||||
/// Notify rasterizer that any caches of the specified region should be invalidated
|
||||
virtual void InvalidateRegion(VAddr addr, u64 size) = 0;
|
||||
virtual void InvalidateRegion(CacheAddr addr, u64 size) = 0;
|
||||
|
||||
/// Notify rasterizer that any caches of the specified region should be flushed and invalidated
|
||||
virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0;
|
||||
virtual void FlushAndInvalidateRegion(CacheAddr addr, u64 size) = 0;
|
||||
|
||||
private:
|
||||
void ProcessBindMethod(const MethodCall& method_call);
|
||||
|
||||
@@ -22,15 +22,15 @@ void GPUAsynch::SwapBuffers(
|
||||
gpu_thread.SwapBuffers(std::move(framebuffer));
|
||||
}
|
||||
|
||||
void GPUAsynch::FlushRegion(VAddr addr, u64 size) {
|
||||
void GPUAsynch::FlushRegion(CacheAddr addr, u64 size) {
|
||||
gpu_thread.FlushRegion(addr, size);
|
||||
}
|
||||
|
||||
void GPUAsynch::InvalidateRegion(VAddr addr, u64 size) {
|
||||
void GPUAsynch::InvalidateRegion(CacheAddr addr, u64 size) {
|
||||
gpu_thread.InvalidateRegion(addr, size);
|
||||
}
|
||||
|
||||
void GPUAsynch::FlushAndInvalidateRegion(VAddr addr, u64 size) {
|
||||
void GPUAsynch::FlushAndInvalidateRegion(CacheAddr addr, u64 size) {
|
||||
gpu_thread.FlushAndInvalidateRegion(addr, size);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@ public:
|
||||
void PushGPUEntries(Tegra::CommandList&& entries) override;
|
||||
void SwapBuffers(
|
||||
std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) override;
|
||||
void FlushRegion(VAddr addr, u64 size) override;
|
||||
void InvalidateRegion(VAddr addr, u64 size) override;
|
||||
void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
|
||||
void FlushRegion(CacheAddr addr, u64 size) override;
|
||||
void InvalidateRegion(CacheAddr addr, u64 size) override;
|
||||
void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override;
|
||||
|
||||
private:
|
||||
GPUThread::ThreadManager gpu_thread;
|
||||
|
||||
@@ -22,15 +22,15 @@ void GPUSynch::SwapBuffers(
|
||||
renderer.SwapBuffers(std::move(framebuffer));
|
||||
}
|
||||
|
||||
void GPUSynch::FlushRegion(VAddr addr, u64 size) {
|
||||
void GPUSynch::FlushRegion(CacheAddr addr, u64 size) {
|
||||
renderer.Rasterizer().FlushRegion(addr, size);
|
||||
}
|
||||
|
||||
void GPUSynch::InvalidateRegion(VAddr addr, u64 size) {
|
||||
void GPUSynch::InvalidateRegion(CacheAddr addr, u64 size) {
|
||||
renderer.Rasterizer().InvalidateRegion(addr, size);
|
||||
}
|
||||
|
||||
void GPUSynch::FlushAndInvalidateRegion(VAddr addr, u64 size) {
|
||||
void GPUSynch::FlushAndInvalidateRegion(CacheAddr addr, u64 size) {
|
||||
renderer.Rasterizer().FlushAndInvalidateRegion(addr, size);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@ public:
|
||||
void PushGPUEntries(Tegra::CommandList&& entries) override;
|
||||
void SwapBuffers(
|
||||
std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) override;
|
||||
void FlushRegion(VAddr addr, u64 size) override;
|
||||
void InvalidateRegion(VAddr addr, u64 size) override;
|
||||
void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
|
||||
void FlushRegion(CacheAddr addr, u64 size) override;
|
||||
void InvalidateRegion(CacheAddr addr, u64 size) override;
|
||||
void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "core/frontend/scope_acquire_window_context.h"
|
||||
#include "core/settings.h"
|
||||
#include "video_core/dma_pusher.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/gpu_thread.h"
|
||||
@@ -13,38 +12,13 @@
|
||||
|
||||
namespace VideoCommon::GPUThread {
|
||||
|
||||
/// Executes a single GPU thread command
|
||||
static void ExecuteCommand(CommandData* command, VideoCore::RendererBase& renderer,
|
||||
Tegra::DmaPusher& dma_pusher) {
|
||||
if (const auto submit_list = std::get_if<SubmitListCommand>(command)) {
|
||||
dma_pusher.Push(std::move(submit_list->entries));
|
||||
dma_pusher.DispatchCalls();
|
||||
} else if (const auto data = std::get_if<SwapBuffersCommand>(command)) {
|
||||
renderer.SwapBuffers(data->framebuffer);
|
||||
} else if (const auto data = std::get_if<FlushRegionCommand>(command)) {
|
||||
renderer.Rasterizer().FlushRegion(data->addr, data->size);
|
||||
} else if (const auto data = std::get_if<InvalidateRegionCommand>(command)) {
|
||||
renderer.Rasterizer().InvalidateRegion(data->addr, data->size);
|
||||
} else if (const auto data = std::get_if<FlushAndInvalidateRegionCommand>(command)) {
|
||||
renderer.Rasterizer().FlushAndInvalidateRegion(data->addr, data->size);
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the GPU thread
|
||||
static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_pusher,
|
||||
SynchState& state) {
|
||||
|
||||
MicroProfileOnThreadCreate("GpuThread");
|
||||
|
||||
auto WaitForWakeup = [&]() {
|
||||
std::unique_lock<std::mutex> lock{state.signal_mutex};
|
||||
state.signal_condition.wait(lock, [&] { return !state.is_idle || !state.is_running; });
|
||||
};
|
||||
|
||||
// Wait for first GPU command before acquiring the window context
|
||||
WaitForWakeup();
|
||||
state.WaitForCommands();
|
||||
|
||||
// If emulation was stopped during disk shader loading, abort before trying to acquire context
|
||||
if (!state.is_running) {
|
||||
@@ -53,100 +27,72 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p
|
||||
|
||||
Core::Frontend::ScopeAcquireWindowContext acquire_context{renderer.GetRenderWindow()};
|
||||
|
||||
CommandDataContainer next;
|
||||
while (state.is_running) {
|
||||
if (!state.is_running) {
|
||||
return;
|
||||
state.WaitForCommands();
|
||||
while (!state.queue.Empty()) {
|
||||
state.queue.Pop(next);
|
||||
if (const auto submit_list = std::get_if<SubmitListCommand>(&next.data)) {
|
||||
dma_pusher.Push(std::move(submit_list->entries));
|
||||
dma_pusher.DispatchCalls();
|
||||
} else if (const auto data = std::get_if<SwapBuffersCommand>(&next.data)) {
|
||||
state.DecrementFramesCounter();
|
||||
renderer.SwapBuffers(std::move(data->framebuffer));
|
||||
} else if (const auto data = std::get_if<FlushRegionCommand>(&next.data)) {
|
||||
renderer.Rasterizer().FlushRegion(data->addr, data->size);
|
||||
} else if (const auto data = std::get_if<InvalidateRegionCommand>(&next.data)) {
|
||||
renderer.Rasterizer().InvalidateRegion(data->addr, data->size);
|
||||
} else if (const auto data = std::get_if<EndProcessingCommand>(&next.data)) {
|
||||
return;
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Thread has been woken up, so make the previous write queue the next read queue
|
||||
std::lock_guard<std::mutex> lock{state.signal_mutex};
|
||||
std::swap(state.push_queue, state.pop_queue);
|
||||
}
|
||||
|
||||
// Execute all of the GPU commands
|
||||
while (!state.pop_queue->empty()) {
|
||||
ExecuteCommand(&state.pop_queue->front(), renderer, dma_pusher);
|
||||
state.pop_queue->pop();
|
||||
}
|
||||
|
||||
state.UpdateIdleState();
|
||||
|
||||
// Signal that the GPU thread has finished processing commands
|
||||
if (state.is_idle) {
|
||||
state.idle_condition.notify_one();
|
||||
}
|
||||
|
||||
// Wait for CPU thread to send more GPU commands
|
||||
WaitForWakeup();
|
||||
}
|
||||
}
|
||||
|
||||
ThreadManager::ThreadManager(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_pusher)
|
||||
: renderer{renderer}, dma_pusher{dma_pusher}, thread{RunThread, std::ref(renderer),
|
||||
std::ref(dma_pusher), std::ref(state)},
|
||||
thread_id{thread.get_id()} {}
|
||||
std::ref(dma_pusher), std::ref(state)} {}
|
||||
|
||||
ThreadManager::~ThreadManager() {
|
||||
{
|
||||
// Notify GPU thread that a shutdown is pending
|
||||
std::lock_guard<std::mutex> lock{state.signal_mutex};
|
||||
state.is_running = false;
|
||||
}
|
||||
|
||||
state.signal_condition.notify_one();
|
||||
// Notify GPU thread that a shutdown is pending
|
||||
PushCommand(EndProcessingCommand());
|
||||
thread.join();
|
||||
}
|
||||
|
||||
void ThreadManager::SubmitList(Tegra::CommandList&& entries) {
|
||||
if (entries.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PushCommand(SubmitListCommand(std::move(entries)), false, false);
|
||||
PushCommand(SubmitListCommand(std::move(entries)));
|
||||
}
|
||||
|
||||
void ThreadManager::SwapBuffers(
|
||||
std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) {
|
||||
PushCommand(SwapBuffersCommand(std::move(framebuffer)), true, false);
|
||||
state.IncrementFramesCounter();
|
||||
PushCommand(SwapBuffersCommand(std::move(framebuffer)));
|
||||
state.WaitForFrames();
|
||||
}
|
||||
|
||||
void ThreadManager::FlushRegion(VAddr addr, u64 size) {
|
||||
// Block the CPU when using accurate emulation
|
||||
PushCommand(FlushRegionCommand(addr, size), Settings::values.use_accurate_gpu_emulation, false);
|
||||
void ThreadManager::FlushRegion(CacheAddr addr, u64 size) {
|
||||
PushCommand(FlushRegionCommand(addr, size));
|
||||
}
|
||||
|
||||
void ThreadManager::InvalidateRegion(VAddr addr, u64 size) {
|
||||
PushCommand(InvalidateRegionCommand(addr, size), true, true);
|
||||
void ThreadManager::InvalidateRegion(CacheAddr addr, u64 size) {
|
||||
if (state.queue.Empty()) {
|
||||
// It's quicker to invalidate a single region on the CPU if the queue is already empty
|
||||
renderer.Rasterizer().InvalidateRegion(addr, size);
|
||||
} else {
|
||||
PushCommand(InvalidateRegionCommand(addr, size));
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadManager::FlushAndInvalidateRegion(VAddr addr, u64 size) {
|
||||
void ThreadManager::FlushAndInvalidateRegion(CacheAddr addr, u64 size) {
|
||||
// Skip flush on asynch mode, as FlushAndInvalidateRegion is not used for anything too important
|
||||
InvalidateRegion(addr, size);
|
||||
}
|
||||
|
||||
void ThreadManager::PushCommand(CommandData&& command_data, bool wait_for_idle, bool allow_on_cpu) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{state.signal_mutex};
|
||||
|
||||
if ((allow_on_cpu && state.is_idle) || IsGpuThread()) {
|
||||
// Execute the command synchronously on the current thread
|
||||
ExecuteCommand(&command_data, renderer, dma_pusher);
|
||||
return;
|
||||
}
|
||||
|
||||
// Push the command to the GPU thread
|
||||
state.UpdateIdleState();
|
||||
state.push_queue->emplace(command_data);
|
||||
}
|
||||
|
||||
// Signal the GPU thread that commands are pending
|
||||
state.signal_condition.notify_one();
|
||||
|
||||
if (wait_for_idle) {
|
||||
// Wait for the GPU to be idle (all commands to be executed)
|
||||
std::unique_lock<std::mutex> lock{state.idle_mutex};
|
||||
state.idle_condition.wait(lock, [this] { return static_cast<bool>(state.is_idle); });
|
||||
}
|
||||
void ThreadManager::PushCommand(CommandData&& command_data) {
|
||||
state.queue.Push(CommandDataContainer(std::move(command_data)));
|
||||
state.SignalCommands();
|
||||
}
|
||||
|
||||
} // namespace VideoCommon::GPUThread
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
#include <thread>
|
||||
#include <variant>
|
||||
|
||||
#include "common/threadsafe_queue.h"
|
||||
#include "video_core/gpu.h"
|
||||
|
||||
namespace Tegra {
|
||||
struct FramebufferConfig;
|
||||
class DmaPusher;
|
||||
@@ -24,6 +27,9 @@ class RendererBase;
|
||||
|
||||
namespace VideoCommon::GPUThread {
|
||||
|
||||
/// Command to signal to the GPU thread that processing has ended
|
||||
struct EndProcessingCommand final {};
|
||||
|
||||
/// Command to signal to the GPU thread that a command list is ready for processing
|
||||
struct SubmitListCommand final {
|
||||
explicit SubmitListCommand(Tegra::CommandList&& entries) : entries{std::move(entries)} {}
|
||||
@@ -36,59 +42,110 @@ struct SwapBuffersCommand final {
|
||||
explicit SwapBuffersCommand(std::optional<const Tegra::FramebufferConfig> framebuffer)
|
||||
: framebuffer{std::move(framebuffer)} {}
|
||||
|
||||
std::optional<const Tegra::FramebufferConfig> framebuffer;
|
||||
std::optional<Tegra::FramebufferConfig> framebuffer;
|
||||
};
|
||||
|
||||
/// Command to signal to the GPU thread to flush a region
|
||||
struct FlushRegionCommand final {
|
||||
explicit constexpr FlushRegionCommand(VAddr addr, u64 size) : addr{addr}, size{size} {}
|
||||
explicit constexpr FlushRegionCommand(CacheAddr addr, u64 size) : addr{addr}, size{size} {}
|
||||
|
||||
const VAddr addr;
|
||||
const u64 size;
|
||||
CacheAddr addr;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
/// Command to signal to the GPU thread to invalidate a region
|
||||
struct InvalidateRegionCommand final {
|
||||
explicit constexpr InvalidateRegionCommand(VAddr addr, u64 size) : addr{addr}, size{size} {}
|
||||
explicit constexpr InvalidateRegionCommand(CacheAddr addr, u64 size) : addr{addr}, size{size} {}
|
||||
|
||||
const VAddr addr;
|
||||
const u64 size;
|
||||
CacheAddr addr;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
/// Command to signal to the GPU thread to flush and invalidate a region
|
||||
struct FlushAndInvalidateRegionCommand final {
|
||||
explicit constexpr FlushAndInvalidateRegionCommand(VAddr addr, u64 size)
|
||||
explicit constexpr FlushAndInvalidateRegionCommand(CacheAddr addr, u64 size)
|
||||
: addr{addr}, size{size} {}
|
||||
|
||||
const VAddr addr;
|
||||
const u64 size;
|
||||
CacheAddr addr;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
using CommandData = std::variant<SubmitListCommand, SwapBuffersCommand, FlushRegionCommand,
|
||||
InvalidateRegionCommand, FlushAndInvalidateRegionCommand>;
|
||||
using CommandData =
|
||||
std::variant<EndProcessingCommand, SubmitListCommand, SwapBuffersCommand, FlushRegionCommand,
|
||||
InvalidateRegionCommand, FlushAndInvalidateRegionCommand>;
|
||||
|
||||
struct CommandDataContainer {
|
||||
CommandDataContainer() = default;
|
||||
|
||||
CommandDataContainer(CommandData&& data) : data{std::move(data)} {}
|
||||
|
||||
CommandDataContainer& operator=(const CommandDataContainer& t) {
|
||||
data = std::move(t.data);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandData data;
|
||||
};
|
||||
|
||||
/// Struct used to synchronize the GPU thread
|
||||
struct SynchState final {
|
||||
std::atomic<bool> is_running{true};
|
||||
std::atomic<bool> is_idle{true};
|
||||
std::condition_variable signal_condition;
|
||||
std::mutex signal_mutex;
|
||||
std::condition_variable idle_condition;
|
||||
std::mutex idle_mutex;
|
||||
std::atomic_bool is_running{true};
|
||||
std::atomic_int queued_frame_count{};
|
||||
std::mutex frames_mutex;
|
||||
std::mutex commands_mutex;
|
||||
std::condition_variable commands_condition;
|
||||
std::condition_variable frames_condition;
|
||||
|
||||
// We use two queues for sending commands to the GPU thread, one for writing (push_queue) to and
|
||||
// one for reading from (pop_queue). These are swapped whenever the current pop_queue becomes
|
||||
// empty. This allows for efficient thread-safe access, as it does not require any copies.
|
||||
|
||||
using CommandQueue = std::queue<CommandData>;
|
||||
std::array<CommandQueue, 2> command_queues;
|
||||
CommandQueue* push_queue{&command_queues[0]};
|
||||
CommandQueue* pop_queue{&command_queues[1]};
|
||||
|
||||
void UpdateIdleState() {
|
||||
std::lock_guard<std::mutex> lock{idle_mutex};
|
||||
is_idle = command_queues[0].empty() && command_queues[1].empty();
|
||||
void IncrementFramesCounter() {
|
||||
std::lock_guard<std::mutex> lock{frames_mutex};
|
||||
++queued_frame_count;
|
||||
}
|
||||
|
||||
void DecrementFramesCounter() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{frames_mutex};
|
||||
--queued_frame_count;
|
||||
|
||||
if (queued_frame_count) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
frames_condition.notify_one();
|
||||
}
|
||||
|
||||
void WaitForFrames() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{frames_mutex};
|
||||
if (!queued_frame_count) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the GPU to be idle (all commands to be executed)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock{frames_mutex};
|
||||
frames_condition.wait(lock, [this] { return !queued_frame_count; });
|
||||
}
|
||||
}
|
||||
|
||||
void SignalCommands() {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock{commands_mutex};
|
||||
if (queue.Empty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
commands_condition.notify_one();
|
||||
}
|
||||
|
||||
void WaitForCommands() {
|
||||
std::unique_lock<std::mutex> lock{commands_mutex};
|
||||
commands_condition.wait(lock, [this] { return !queue.Empty(); });
|
||||
}
|
||||
|
||||
using CommandQueue = Common::SPSCQueue<CommandDataContainer>;
|
||||
CommandQueue queue;
|
||||
};
|
||||
|
||||
/// Class used to manage the GPU thread
|
||||
@@ -105,22 +162,17 @@ public:
|
||||
std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer);
|
||||
|
||||
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
|
||||
void FlushRegion(VAddr addr, u64 size);
|
||||
void FlushRegion(CacheAddr addr, u64 size);
|
||||
|
||||
/// Notify rasterizer that any caches of the specified region should be invalidated
|
||||
void InvalidateRegion(VAddr addr, u64 size);
|
||||
void InvalidateRegion(CacheAddr addr, u64 size);
|
||||
|
||||
/// Notify rasterizer that any caches of the specified region should be flushed and invalidated
|
||||
void FlushAndInvalidateRegion(VAddr addr, u64 size);
|
||||
void FlushAndInvalidateRegion(CacheAddr addr, u64 size);
|
||||
|
||||
private:
|
||||
/// Pushes a command to be executed by the GPU thread
|
||||
void PushCommand(CommandData&& command_data, bool wait_for_idle, bool allow_on_cpu);
|
||||
|
||||
/// Returns true if this is called by the GPU thread
|
||||
bool IsGpuThread() const {
|
||||
return std::this_thread::get_id() == thread_id;
|
||||
}
|
||||
void PushCommand(CommandData&& command_data);
|
||||
|
||||
private:
|
||||
SynchState state;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
|
||||
namespace Tegra {
|
||||
@@ -162,15 +163,51 @@ std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) {
|
||||
return base_addr + (gpu_addr & PAGE_MASK);
|
||||
}
|
||||
|
||||
std::vector<GPUVAddr> MemoryManager::CpuToGpuAddress(VAddr cpu_addr) const {
|
||||
std::vector<GPUVAddr> results;
|
||||
for (const auto& region : mapped_regions) {
|
||||
if (cpu_addr >= region.cpu_addr && cpu_addr < (region.cpu_addr + region.size)) {
|
||||
const u64 offset{cpu_addr - region.cpu_addr};
|
||||
results.push_back(region.gpu_addr + offset);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
u8 MemoryManager::Read8(GPUVAddr addr) {
|
||||
return Memory::Read8(*GpuToCpuAddress(addr));
|
||||
}
|
||||
|
||||
u16 MemoryManager::Read16(GPUVAddr addr) {
|
||||
return Memory::Read16(*GpuToCpuAddress(addr));
|
||||
}
|
||||
|
||||
u32 MemoryManager::Read32(GPUVAddr addr) {
|
||||
return Memory::Read32(*GpuToCpuAddress(addr));
|
||||
}
|
||||
|
||||
u64 MemoryManager::Read64(GPUVAddr addr) {
|
||||
return Memory::Read64(*GpuToCpuAddress(addr));
|
||||
}
|
||||
|
||||
void MemoryManager::Write8(GPUVAddr addr, u8 data) {
|
||||
Memory::Write8(*GpuToCpuAddress(addr), data);
|
||||
}
|
||||
|
||||
void MemoryManager::Write16(GPUVAddr addr, u16 data) {
|
||||
Memory::Write16(*GpuToCpuAddress(addr), data);
|
||||
}
|
||||
|
||||
void MemoryManager::Write32(GPUVAddr addr, u32 data) {
|
||||
Memory::Write32(*GpuToCpuAddress(addr), data);
|
||||
}
|
||||
|
||||
void MemoryManager::Write64(GPUVAddr addr, u64 data) {
|
||||
Memory::Write64(*GpuToCpuAddress(addr), data);
|
||||
}
|
||||
|
||||
u8* MemoryManager::GetPointer(GPUVAddr addr) {
|
||||
return Memory::GetPointer(*GpuToCpuAddress(addr));
|
||||
}
|
||||
|
||||
void MemoryManager::ReadBlock(GPUVAddr src_addr, void* dest_buffer, std::size_t size) {
|
||||
std::memcpy(dest_buffer, GetPointer(src_addr), size);
|
||||
}
|
||||
void MemoryManager::WriteBlock(GPUVAddr dest_addr, const void* src_buffer, std::size_t size) {
|
||||
std::memcpy(GetPointer(dest_addr), src_buffer, size);
|
||||
}
|
||||
|
||||
void MemoryManager::CopyBlock(GPUVAddr dest_addr, GPUVAddr src_addr, std::size_t size) {
|
||||
std::memcpy(GetPointer(dest_addr), GetPointer(src_addr), size);
|
||||
}
|
||||
|
||||
VAddr& MemoryManager::PageSlot(GPUVAddr gpu_addr) {
|
||||
|
||||
@@ -27,12 +27,27 @@ public:
|
||||
GPUVAddr UnmapBuffer(GPUVAddr gpu_addr, u64 size);
|
||||
GPUVAddr GetRegionEnd(GPUVAddr region_start) const;
|
||||
std::optional<VAddr> GpuToCpuAddress(GPUVAddr gpu_addr);
|
||||
std::vector<GPUVAddr> CpuToGpuAddress(VAddr cpu_addr) const;
|
||||
|
||||
static constexpr u64 PAGE_BITS = 16;
|
||||
static constexpr u64 PAGE_SIZE = 1 << PAGE_BITS;
|
||||
static constexpr u64 PAGE_MASK = PAGE_SIZE - 1;
|
||||
|
||||
u8 Read8(GPUVAddr addr);
|
||||
u16 Read16(GPUVAddr addr);
|
||||
u32 Read32(GPUVAddr addr);
|
||||
u64 Read64(GPUVAddr addr);
|
||||
|
||||
void Write8(GPUVAddr addr, u8 data);
|
||||
void Write16(GPUVAddr addr, u16 data);
|
||||
void Write32(GPUVAddr addr, u32 data);
|
||||
void Write64(GPUVAddr addr, u64 data);
|
||||
|
||||
u8* GetPointer(GPUVAddr vaddr);
|
||||
|
||||
void ReadBlock(GPUVAddr src_addr, void* dest_buffer, std::size_t size);
|
||||
void WriteBlock(GPUVAddr dest_addr, const void* src_buffer, std::size_t size);
|
||||
void CopyBlock(VAddr dest_addr, VAddr src_addr, std::size_t size);
|
||||
|
||||
private:
|
||||
enum class PageStatus : u64 {
|
||||
Unmapped = 0xFFFFFFFFFFFFFFFFULL,
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <cstring>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/morton.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
@@ -16,12 +15,12 @@ namespace VideoCore {
|
||||
using Surface::GetBytesPerPixel;
|
||||
using Surface::PixelFormat;
|
||||
|
||||
using MortonCopyFn = void (*)(u32, u32, u32, u32, u32, u32, u8*, std::size_t, VAddr);
|
||||
using MortonCopyFn = void (*)(u32, u32, u32, u32, u32, u32, u8*, u8*);
|
||||
using ConversionArray = std::array<MortonCopyFn, Surface::MaxPixelFormat>;
|
||||
|
||||
template <bool morton_to_linear, PixelFormat format>
|
||||
static void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth, u32 depth,
|
||||
u32 tile_width_spacing, u8* buffer, std::size_t buffer_size, VAddr addr) {
|
||||
u32 tile_width_spacing, u8* buffer, u8* addr) {
|
||||
constexpr u32 bytes_per_pixel = GetBytesPerPixel(format);
|
||||
|
||||
// With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
|
||||
@@ -34,150 +33,146 @@ static void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth
|
||||
stride, height, depth, block_height, block_depth,
|
||||
tile_width_spacing);
|
||||
} else {
|
||||
Tegra::Texture::CopySwizzledData(
|
||||
(stride + tile_size_x - 1) / tile_size_x, (height + tile_size_y - 1) / tile_size_y,
|
||||
depth, bytes_per_pixel, bytes_per_pixel, Memory::GetPointer(addr), buffer, false,
|
||||
block_height, block_depth, tile_width_spacing);
|
||||
Tegra::Texture::CopySwizzledData((stride + tile_size_x - 1) / tile_size_x,
|
||||
(height + tile_size_y - 1) / tile_size_y, depth,
|
||||
bytes_per_pixel, bytes_per_pixel, addr, buffer, false,
|
||||
block_height, block_depth, tile_width_spacing);
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr ConversionArray morton_to_linear_fns = {
|
||||
// clang-format off
|
||||
MortonCopy<true, PixelFormat::ABGR8U>,
|
||||
MortonCopy<true, PixelFormat::ABGR8S>,
|
||||
MortonCopy<true, PixelFormat::ABGR8UI>,
|
||||
MortonCopy<true, PixelFormat::B5G6R5U>,
|
||||
MortonCopy<true, PixelFormat::A2B10G10R10U>,
|
||||
MortonCopy<true, PixelFormat::A1B5G5R5U>,
|
||||
MortonCopy<true, PixelFormat::R8U>,
|
||||
MortonCopy<true, PixelFormat::R8UI>,
|
||||
MortonCopy<true, PixelFormat::RGBA16F>,
|
||||
MortonCopy<true, PixelFormat::RGBA16U>,
|
||||
MortonCopy<true, PixelFormat::RGBA16UI>,
|
||||
MortonCopy<true, PixelFormat::R11FG11FB10F>,
|
||||
MortonCopy<true, PixelFormat::RGBA32UI>,
|
||||
MortonCopy<true, PixelFormat::DXT1>,
|
||||
MortonCopy<true, PixelFormat::DXT23>,
|
||||
MortonCopy<true, PixelFormat::DXT45>,
|
||||
MortonCopy<true, PixelFormat::DXN1>,
|
||||
MortonCopy<true, PixelFormat::DXN2UNORM>,
|
||||
MortonCopy<true, PixelFormat::DXN2SNORM>,
|
||||
MortonCopy<true, PixelFormat::BC7U>,
|
||||
MortonCopy<true, PixelFormat::BC6H_UF16>,
|
||||
MortonCopy<true, PixelFormat::BC6H_SF16>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_4X4>,
|
||||
MortonCopy<true, PixelFormat::BGRA8>,
|
||||
MortonCopy<true, PixelFormat::RGBA32F>,
|
||||
MortonCopy<true, PixelFormat::RG32F>,
|
||||
MortonCopy<true, PixelFormat::R32F>,
|
||||
MortonCopy<true, PixelFormat::R16F>,
|
||||
MortonCopy<true, PixelFormat::R16U>,
|
||||
MortonCopy<true, PixelFormat::R16S>,
|
||||
MortonCopy<true, PixelFormat::R16UI>,
|
||||
MortonCopy<true, PixelFormat::R16I>,
|
||||
MortonCopy<true, PixelFormat::RG16>,
|
||||
MortonCopy<true, PixelFormat::RG16F>,
|
||||
MortonCopy<true, PixelFormat::RG16UI>,
|
||||
MortonCopy<true, PixelFormat::RG16I>,
|
||||
MortonCopy<true, PixelFormat::RG16S>,
|
||||
MortonCopy<true, PixelFormat::RGB32F>,
|
||||
MortonCopy<true, PixelFormat::RGBA8_SRGB>,
|
||||
MortonCopy<true, PixelFormat::RG8U>,
|
||||
MortonCopy<true, PixelFormat::RG8S>,
|
||||
MortonCopy<true, PixelFormat::RG32UI>,
|
||||
MortonCopy<true, PixelFormat::R32UI>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_8X8>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_8X5>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_5X4>,
|
||||
MortonCopy<true, PixelFormat::BGRA8_SRGB>,
|
||||
MortonCopy<true, PixelFormat::DXT1_SRGB>,
|
||||
MortonCopy<true, PixelFormat::DXT23_SRGB>,
|
||||
MortonCopy<true, PixelFormat::DXT45_SRGB>,
|
||||
MortonCopy<true, PixelFormat::BC7U_SRGB>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_4X4_SRGB>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_8X8_SRGB>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_8X5_SRGB>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_5X4_SRGB>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_5X5>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_5X5_SRGB>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_10X8>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_10X8_SRGB>,
|
||||
MortonCopy<true, PixelFormat::Z32F>,
|
||||
MortonCopy<true, PixelFormat::Z16>,
|
||||
MortonCopy<true, PixelFormat::Z24S8>,
|
||||
MortonCopy<true, PixelFormat::S8Z24>,
|
||||
MortonCopy<true, PixelFormat::Z32FS8>,
|
||||
// clang-format on
|
||||
MortonCopy<true, PixelFormat::ABGR8U>,
|
||||
MortonCopy<true, PixelFormat::ABGR8S>,
|
||||
MortonCopy<true, PixelFormat::ABGR8UI>,
|
||||
MortonCopy<true, PixelFormat::B5G6R5U>,
|
||||
MortonCopy<true, PixelFormat::A2B10G10R10U>,
|
||||
MortonCopy<true, PixelFormat::A1B5G5R5U>,
|
||||
MortonCopy<true, PixelFormat::R8U>,
|
||||
MortonCopy<true, PixelFormat::R8UI>,
|
||||
MortonCopy<true, PixelFormat::RGBA16F>,
|
||||
MortonCopy<true, PixelFormat::RGBA16U>,
|
||||
MortonCopy<true, PixelFormat::RGBA16UI>,
|
||||
MortonCopy<true, PixelFormat::R11FG11FB10F>,
|
||||
MortonCopy<true, PixelFormat::RGBA32UI>,
|
||||
MortonCopy<true, PixelFormat::DXT1>,
|
||||
MortonCopy<true, PixelFormat::DXT23>,
|
||||
MortonCopy<true, PixelFormat::DXT45>,
|
||||
MortonCopy<true, PixelFormat::DXN1>,
|
||||
MortonCopy<true, PixelFormat::DXN2UNORM>,
|
||||
MortonCopy<true, PixelFormat::DXN2SNORM>,
|
||||
MortonCopy<true, PixelFormat::BC7U>,
|
||||
MortonCopy<true, PixelFormat::BC6H_UF16>,
|
||||
MortonCopy<true, PixelFormat::BC6H_SF16>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_4X4>,
|
||||
MortonCopy<true, PixelFormat::BGRA8>,
|
||||
MortonCopy<true, PixelFormat::RGBA32F>,
|
||||
MortonCopy<true, PixelFormat::RG32F>,
|
||||
MortonCopy<true, PixelFormat::R32F>,
|
||||
MortonCopy<true, PixelFormat::R16F>,
|
||||
MortonCopy<true, PixelFormat::R16U>,
|
||||
MortonCopy<true, PixelFormat::R16S>,
|
||||
MortonCopy<true, PixelFormat::R16UI>,
|
||||
MortonCopy<true, PixelFormat::R16I>,
|
||||
MortonCopy<true, PixelFormat::RG16>,
|
||||
MortonCopy<true, PixelFormat::RG16F>,
|
||||
MortonCopy<true, PixelFormat::RG16UI>,
|
||||
MortonCopy<true, PixelFormat::RG16I>,
|
||||
MortonCopy<true, PixelFormat::RG16S>,
|
||||
MortonCopy<true, PixelFormat::RGB32F>,
|
||||
MortonCopy<true, PixelFormat::RGBA8_SRGB>,
|
||||
MortonCopy<true, PixelFormat::RG8U>,
|
||||
MortonCopy<true, PixelFormat::RG8S>,
|
||||
MortonCopy<true, PixelFormat::RG32UI>,
|
||||
MortonCopy<true, PixelFormat::R32UI>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_8X8>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_8X5>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_5X4>,
|
||||
MortonCopy<true, PixelFormat::BGRA8_SRGB>,
|
||||
MortonCopy<true, PixelFormat::DXT1_SRGB>,
|
||||
MortonCopy<true, PixelFormat::DXT23_SRGB>,
|
||||
MortonCopy<true, PixelFormat::DXT45_SRGB>,
|
||||
MortonCopy<true, PixelFormat::BC7U_SRGB>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_4X4_SRGB>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_8X8_SRGB>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_8X5_SRGB>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_5X4_SRGB>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_5X5>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_5X5_SRGB>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_10X8>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_10X8_SRGB>,
|
||||
MortonCopy<true, PixelFormat::Z32F>,
|
||||
MortonCopy<true, PixelFormat::Z16>,
|
||||
MortonCopy<true, PixelFormat::Z24S8>,
|
||||
MortonCopy<true, PixelFormat::S8Z24>,
|
||||
MortonCopy<true, PixelFormat::Z32FS8>,
|
||||
};
|
||||
|
||||
static constexpr ConversionArray linear_to_morton_fns = {
|
||||
// clang-format off
|
||||
MortonCopy<false, PixelFormat::ABGR8U>,
|
||||
MortonCopy<false, PixelFormat::ABGR8S>,
|
||||
MortonCopy<false, PixelFormat::ABGR8UI>,
|
||||
MortonCopy<false, PixelFormat::B5G6R5U>,
|
||||
MortonCopy<false, PixelFormat::A2B10G10R10U>,
|
||||
MortonCopy<false, PixelFormat::A1B5G5R5U>,
|
||||
MortonCopy<false, PixelFormat::R8U>,
|
||||
MortonCopy<false, PixelFormat::R8UI>,
|
||||
MortonCopy<false, PixelFormat::RGBA16F>,
|
||||
MortonCopy<false, PixelFormat::RGBA16U>,
|
||||
MortonCopy<false, PixelFormat::RGBA16UI>,
|
||||
MortonCopy<false, PixelFormat::R11FG11FB10F>,
|
||||
MortonCopy<false, PixelFormat::RGBA32UI>,
|
||||
MortonCopy<false, PixelFormat::DXT1>,
|
||||
MortonCopy<false, PixelFormat::DXT23>,
|
||||
MortonCopy<false, PixelFormat::DXT45>,
|
||||
MortonCopy<false, PixelFormat::DXN1>,
|
||||
MortonCopy<false, PixelFormat::DXN2UNORM>,
|
||||
MortonCopy<false, PixelFormat::DXN2SNORM>,
|
||||
MortonCopy<false, PixelFormat::BC7U>,
|
||||
MortonCopy<false, PixelFormat::BC6H_UF16>,
|
||||
MortonCopy<false, PixelFormat::BC6H_SF16>,
|
||||
// TODO(Subv): Swizzling ASTC formats are not supported
|
||||
nullptr,
|
||||
MortonCopy<false, PixelFormat::BGRA8>,
|
||||
MortonCopy<false, PixelFormat::RGBA32F>,
|
||||
MortonCopy<false, PixelFormat::RG32F>,
|
||||
MortonCopy<false, PixelFormat::R32F>,
|
||||
MortonCopy<false, PixelFormat::R16F>,
|
||||
MortonCopy<false, PixelFormat::R16U>,
|
||||
MortonCopy<false, PixelFormat::R16S>,
|
||||
MortonCopy<false, PixelFormat::R16UI>,
|
||||
MortonCopy<false, PixelFormat::R16I>,
|
||||
MortonCopy<false, PixelFormat::RG16>,
|
||||
MortonCopy<false, PixelFormat::RG16F>,
|
||||
MortonCopy<false, PixelFormat::RG16UI>,
|
||||
MortonCopy<false, PixelFormat::RG16I>,
|
||||
MortonCopy<false, PixelFormat::RG16S>,
|
||||
MortonCopy<false, PixelFormat::RGB32F>,
|
||||
MortonCopy<false, PixelFormat::RGBA8_SRGB>,
|
||||
MortonCopy<false, PixelFormat::RG8U>,
|
||||
MortonCopy<false, PixelFormat::RG8S>,
|
||||
MortonCopy<false, PixelFormat::RG32UI>,
|
||||
MortonCopy<false, PixelFormat::R32UI>,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
MortonCopy<false, PixelFormat::BGRA8_SRGB>,
|
||||
MortonCopy<false, PixelFormat::DXT1_SRGB>,
|
||||
MortonCopy<false, PixelFormat::DXT23_SRGB>,
|
||||
MortonCopy<false, PixelFormat::DXT45_SRGB>,
|
||||
MortonCopy<false, PixelFormat::BC7U_SRGB>,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
MortonCopy<false, PixelFormat::Z32F>,
|
||||
MortonCopy<false, PixelFormat::Z16>,
|
||||
MortonCopy<false, PixelFormat::Z24S8>,
|
||||
MortonCopy<false, PixelFormat::S8Z24>,
|
||||
MortonCopy<false, PixelFormat::Z32FS8>,
|
||||
// clang-format on
|
||||
MortonCopy<false, PixelFormat::ABGR8U>,
|
||||
MortonCopy<false, PixelFormat::ABGR8S>,
|
||||
MortonCopy<false, PixelFormat::ABGR8UI>,
|
||||
MortonCopy<false, PixelFormat::B5G6R5U>,
|
||||
MortonCopy<false, PixelFormat::A2B10G10R10U>,
|
||||
MortonCopy<false, PixelFormat::A1B5G5R5U>,
|
||||
MortonCopy<false, PixelFormat::R8U>,
|
||||
MortonCopy<false, PixelFormat::R8UI>,
|
||||
MortonCopy<false, PixelFormat::RGBA16F>,
|
||||
MortonCopy<false, PixelFormat::RGBA16U>,
|
||||
MortonCopy<false, PixelFormat::RGBA16UI>,
|
||||
MortonCopy<false, PixelFormat::R11FG11FB10F>,
|
||||
MortonCopy<false, PixelFormat::RGBA32UI>,
|
||||
MortonCopy<false, PixelFormat::DXT1>,
|
||||
MortonCopy<false, PixelFormat::DXT23>,
|
||||
MortonCopy<false, PixelFormat::DXT45>,
|
||||
MortonCopy<false, PixelFormat::DXN1>,
|
||||
MortonCopy<false, PixelFormat::DXN2UNORM>,
|
||||
MortonCopy<false, PixelFormat::DXN2SNORM>,
|
||||
MortonCopy<false, PixelFormat::BC7U>,
|
||||
MortonCopy<false, PixelFormat::BC6H_UF16>,
|
||||
MortonCopy<false, PixelFormat::BC6H_SF16>,
|
||||
// TODO(Subv): Swizzling ASTC formats are not supported
|
||||
nullptr,
|
||||
MortonCopy<false, PixelFormat::BGRA8>,
|
||||
MortonCopy<false, PixelFormat::RGBA32F>,
|
||||
MortonCopy<false, PixelFormat::RG32F>,
|
||||
MortonCopy<false, PixelFormat::R32F>,
|
||||
MortonCopy<false, PixelFormat::R16F>,
|
||||
MortonCopy<false, PixelFormat::R16U>,
|
||||
MortonCopy<false, PixelFormat::R16S>,
|
||||
MortonCopy<false, PixelFormat::R16UI>,
|
||||
MortonCopy<false, PixelFormat::R16I>,
|
||||
MortonCopy<false, PixelFormat::RG16>,
|
||||
MortonCopy<false, PixelFormat::RG16F>,
|
||||
MortonCopy<false, PixelFormat::RG16UI>,
|
||||
MortonCopy<false, PixelFormat::RG16I>,
|
||||
MortonCopy<false, PixelFormat::RG16S>,
|
||||
MortonCopy<false, PixelFormat::RGB32F>,
|
||||
MortonCopy<false, PixelFormat::RGBA8_SRGB>,
|
||||
MortonCopy<false, PixelFormat::RG8U>,
|
||||
MortonCopy<false, PixelFormat::RG8S>,
|
||||
MortonCopy<false, PixelFormat::RG32UI>,
|
||||
MortonCopy<false, PixelFormat::R32UI>,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
MortonCopy<false, PixelFormat::BGRA8_SRGB>,
|
||||
MortonCopy<false, PixelFormat::DXT1_SRGB>,
|
||||
MortonCopy<false, PixelFormat::DXT23_SRGB>,
|
||||
MortonCopy<false, PixelFormat::DXT45_SRGB>,
|
||||
MortonCopy<false, PixelFormat::BC7U_SRGB>,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
MortonCopy<false, PixelFormat::Z32F>,
|
||||
MortonCopy<false, PixelFormat::Z16>,
|
||||
MortonCopy<false, PixelFormat::Z24S8>,
|
||||
MortonCopy<false, PixelFormat::S8Z24>,
|
||||
MortonCopy<false, PixelFormat::Z32FS8>,
|
||||
};
|
||||
|
||||
static MortonCopyFn GetSwizzleFunction(MortonSwizzleMode mode, Surface::PixelFormat format) {
|
||||
@@ -191,45 +186,6 @@ static MortonCopyFn GetSwizzleFunction(MortonSwizzleMode mode, Surface::PixelFor
|
||||
return morton_to_linear_fns[static_cast<std::size_t>(format)];
|
||||
}
|
||||
|
||||
/// 8x8 Z-Order coordinate from 2D coordinates
|
||||
static u32 MortonInterleave(u32 x, u32 y) {
|
||||
static const u32 xlut[] = {0x00, 0x01, 0x04, 0x05, 0x10, 0x11, 0x14, 0x15};
|
||||
static const u32 ylut[] = {0x00, 0x02, 0x08, 0x0a, 0x20, 0x22, 0x28, 0x2a};
|
||||
return xlut[x % 8] + ylut[y % 8];
|
||||
}
|
||||
|
||||
/// Calculates the offset of the position of the pixel in Morton order
|
||||
static u32 GetMortonOffset(u32 x, u32 y, u32 bytes_per_pixel) {
|
||||
// Images are split into 8x8 tiles. Each tile is composed of four 4x4 subtiles each
|
||||
// of which is composed of four 2x2 subtiles each of which is composed of four texels.
|
||||
// Each structure is embedded into the next-bigger one in a diagonal pattern, e.g.
|
||||
// texels are laid out in a 2x2 subtile like this:
|
||||
// 2 3
|
||||
// 0 1
|
||||
//
|
||||
// The full 8x8 tile has the texels arranged like this:
|
||||
//
|
||||
// 42 43 46 47 58 59 62 63
|
||||
// 40 41 44 45 56 57 60 61
|
||||
// 34 35 38 39 50 51 54 55
|
||||
// 32 33 36 37 48 49 52 53
|
||||
// 10 11 14 15 26 27 30 31
|
||||
// 08 09 12 13 24 25 28 29
|
||||
// 02 03 06 07 18 19 22 23
|
||||
// 00 01 04 05 16 17 20 21
|
||||
//
|
||||
// This pattern is what's called Z-order curve, or Morton order.
|
||||
|
||||
const unsigned int block_height = 8;
|
||||
const unsigned int coarse_x = x & ~7;
|
||||
|
||||
u32 i = MortonInterleave(x, y);
|
||||
|
||||
const unsigned int offset = coarse_x * block_height;
|
||||
|
||||
return (i + offset) * bytes_per_pixel;
|
||||
}
|
||||
|
||||
static u32 MortonInterleave128(u32 x, u32 y) {
|
||||
// 128x128 Z-Order coordinate from 2D coordinates
|
||||
static constexpr u32 xlut[] = {
|
||||
@@ -325,14 +281,14 @@ static u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) {
|
||||
|
||||
void MortonSwizzle(MortonSwizzleMode mode, Surface::PixelFormat format, u32 stride,
|
||||
u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing,
|
||||
u8* buffer, std::size_t buffer_size, VAddr addr) {
|
||||
|
||||
u8* buffer, u8* addr) {
|
||||
GetSwizzleFunction(mode, format)(stride, block_height, height, block_depth, depth,
|
||||
tile_width_spacing, buffer, buffer_size, addr);
|
||||
tile_width_spacing, buffer, addr);
|
||||
}
|
||||
|
||||
void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel, u32 linear_bytes_per_pixel,
|
||||
u8* morton_data, u8* linear_data, bool morton_to_linear) {
|
||||
void MortonCopyPixels128(MortonSwizzleMode mode, u32 width, u32 height, u32 bytes_per_pixel,
|
||||
u32 linear_bytes_per_pixel, u8* morton_data, u8* linear_data) {
|
||||
const bool morton_to_linear = mode == MortonSwizzleMode::MortonToLinear;
|
||||
u8* data_ptrs[2];
|
||||
for (u32 y = 0; y < height; ++y) {
|
||||
for (u32 x = 0; x < width; ++x) {
|
||||
|
||||
@@ -13,9 +13,9 @@ enum class MortonSwizzleMode { MortonToLinear, LinearToMorton };
|
||||
|
||||
void MortonSwizzle(MortonSwizzleMode mode, VideoCore::Surface::PixelFormat format, u32 stride,
|
||||
u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing,
|
||||
u8* buffer, std::size_t buffer_size, VAddr addr);
|
||||
u8* buffer, u8* addr);
|
||||
|
||||
void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel, u32 linear_bytes_per_pixel,
|
||||
u8* morton_data, u8* linear_data, bool morton_to_linear);
|
||||
void MortonCopyPixels128(MortonSwizzleMode mode, u32 width, u32 height, u32 bytes_per_pixel,
|
||||
u32 linear_bytes_per_pixel, u8* morton_data, u8* linear_data);
|
||||
|
||||
} // namespace VideoCore
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
@@ -12,14 +13,26 @@
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/settings.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
|
||||
class RasterizerCacheObject {
|
||||
public:
|
||||
explicit RasterizerCacheObject(const u8* host_ptr)
|
||||
: host_ptr{host_ptr}, cache_addr{ToCacheAddr(host_ptr)} {}
|
||||
|
||||
virtual ~RasterizerCacheObject();
|
||||
|
||||
CacheAddr GetCacheAddr() const {
|
||||
return cache_addr;
|
||||
}
|
||||
|
||||
const u8* GetHostPtr() const {
|
||||
return host_ptr;
|
||||
}
|
||||
|
||||
/// Gets the address of the shader in guest memory, required for cache management
|
||||
virtual VAddr GetAddr() const = 0;
|
||||
virtual VAddr GetCpuAddr() const = 0;
|
||||
|
||||
/// Gets the size of the shader in guest memory, required for cache management
|
||||
virtual std::size_t GetSizeInBytes() const = 0;
|
||||
@@ -58,6 +71,8 @@ private:
|
||||
bool is_registered{}; ///< Whether the object is currently registered with the cache
|
||||
bool is_dirty{}; ///< Whether the object is dirty (out of sync with guest memory)
|
||||
u64 last_modified_ticks{}; ///< When the object was last modified, used for in-order flushing
|
||||
CacheAddr cache_addr{}; ///< Cache address memory, unique from emulated virtual address space
|
||||
const u8* host_ptr{}; ///< Pointer to the memory backing this cached region
|
||||
};
|
||||
|
||||
template <class T>
|
||||
@@ -68,7 +83,9 @@ public:
|
||||
explicit RasterizerCache(VideoCore::RasterizerInterface& rasterizer) : rasterizer{rasterizer} {}
|
||||
|
||||
/// Write any cached resources overlapping the specified region back to memory
|
||||
void FlushRegion(Tegra::GPUVAddr addr, size_t size) {
|
||||
void FlushRegion(CacheAddr addr, std::size_t size) {
|
||||
std::lock_guard<std::recursive_mutex> lock{mutex};
|
||||
|
||||
const auto& objects{GetSortedObjectsFromRegion(addr, size)};
|
||||
for (auto& object : objects) {
|
||||
FlushObject(object);
|
||||
@@ -76,7 +93,9 @@ public:
|
||||
}
|
||||
|
||||
/// Mark the specified region as being invalidated
|
||||
void InvalidateRegion(VAddr addr, u64 size) {
|
||||
void InvalidateRegion(CacheAddr addr, u64 size) {
|
||||
std::lock_guard<std::recursive_mutex> lock{mutex};
|
||||
|
||||
const auto& objects{GetSortedObjectsFromRegion(addr, size)};
|
||||
for (auto& object : objects) {
|
||||
if (!object->IsRegistered()) {
|
||||
@@ -89,48 +108,60 @@ public:
|
||||
|
||||
/// Invalidates everything in the cache
|
||||
void InvalidateAll() {
|
||||
std::lock_guard<std::recursive_mutex> lock{mutex};
|
||||
|
||||
while (interval_cache.begin() != interval_cache.end()) {
|
||||
Unregister(*interval_cache.begin()->second.begin());
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
/// Tries to get an object from the cache with the specified address
|
||||
T TryGet(VAddr addr) const {
|
||||
/// Tries to get an object from the cache with the specified cache address
|
||||
T TryGet(CacheAddr addr) const {
|
||||
const auto iter = map_cache.find(addr);
|
||||
if (iter != map_cache.end())
|
||||
return iter->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
T TryGet(const void* addr) const {
|
||||
const auto iter = map_cache.find(ToCacheAddr(addr));
|
||||
if (iter != map_cache.end())
|
||||
return iter->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// Register an object into the cache
|
||||
void Register(const T& object) {
|
||||
std::lock_guard<std::recursive_mutex> lock{mutex};
|
||||
|
||||
object->SetIsRegistered(true);
|
||||
interval_cache.add({GetInterval(object), ObjectSet{object}});
|
||||
map_cache.insert({object->GetAddr(), object});
|
||||
rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), 1);
|
||||
map_cache.insert({object->GetCacheAddr(), object});
|
||||
rasterizer.UpdatePagesCachedCount(object->GetCpuAddr(), object->GetSizeInBytes(), 1);
|
||||
}
|
||||
|
||||
/// Unregisters an object from the cache
|
||||
void Unregister(const T& object) {
|
||||
object->SetIsRegistered(false);
|
||||
rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), -1);
|
||||
// Only flush if use_accurate_gpu_emulation is enabled, as it incurs a performance hit
|
||||
if (Settings::values.use_accurate_gpu_emulation) {
|
||||
FlushObject(object);
|
||||
}
|
||||
std::lock_guard<std::recursive_mutex> lock{mutex};
|
||||
|
||||
object->SetIsRegistered(false);
|
||||
rasterizer.UpdatePagesCachedCount(object->GetCpuAddr(), object->GetSizeInBytes(), -1);
|
||||
interval_cache.subtract({GetInterval(object), ObjectSet{object}});
|
||||
map_cache.erase(object->GetAddr());
|
||||
map_cache.erase(object->GetCacheAddr());
|
||||
}
|
||||
|
||||
/// Returns a ticks counter used for tracking when cached objects were last modified
|
||||
u64 GetModifiedTicks() {
|
||||
std::lock_guard<std::recursive_mutex> lock{mutex};
|
||||
|
||||
return ++modified_ticks;
|
||||
}
|
||||
|
||||
/// Flushes the specified object, updating appropriate cache state as needed
|
||||
void FlushObject(const T& object) {
|
||||
std::lock_guard<std::recursive_mutex> lock{mutex};
|
||||
|
||||
if (!object->IsDirty()) {
|
||||
return;
|
||||
}
|
||||
@@ -140,7 +171,7 @@ protected:
|
||||
|
||||
private:
|
||||
/// Returns a list of cached objects from the specified memory region, ordered by access time
|
||||
std::vector<T> GetSortedObjectsFromRegion(VAddr addr, u64 size) {
|
||||
std::vector<T> GetSortedObjectsFromRegion(CacheAddr addr, u64 size) {
|
||||
if (size == 0) {
|
||||
return {};
|
||||
}
|
||||
@@ -164,17 +195,18 @@ private:
|
||||
}
|
||||
|
||||
using ObjectSet = std::set<T>;
|
||||
using ObjectCache = std::unordered_map<VAddr, T>;
|
||||
using IntervalCache = boost::icl::interval_map<VAddr, ObjectSet>;
|
||||
using ObjectCache = std::unordered_map<CacheAddr, T>;
|
||||
using IntervalCache = boost::icl::interval_map<CacheAddr, ObjectSet>;
|
||||
using ObjectInterval = typename IntervalCache::interval_type;
|
||||
|
||||
static auto GetInterval(const T& object) {
|
||||
return ObjectInterval::right_open(object->GetAddr(),
|
||||
object->GetAddr() + object->GetSizeInBytes());
|
||||
return ObjectInterval::right_open(object->GetCacheAddr(),
|
||||
object->GetCacheAddr() + object->GetSizeInBytes());
|
||||
}
|
||||
|
||||
ObjectCache map_cache;
|
||||
IntervalCache interval_cache; ///< Cache of objects
|
||||
u64 modified_ticks{}; ///< Counter of cache state ticks, used for in-order flushing
|
||||
VideoCore::RasterizerInterface& rasterizer;
|
||||
std::recursive_mutex mutex;
|
||||
};
|
||||
|
||||
@@ -35,14 +35,14 @@ public:
|
||||
virtual void FlushAll() = 0;
|
||||
|
||||
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
|
||||
virtual void FlushRegion(VAddr addr, u64 size) = 0;
|
||||
virtual void FlushRegion(CacheAddr addr, u64 size) = 0;
|
||||
|
||||
/// Notify rasterizer that any caches of the specified region should be invalidated
|
||||
virtual void InvalidateRegion(VAddr addr, u64 size) = 0;
|
||||
virtual void InvalidateRegion(CacheAddr addr, u64 size) = 0;
|
||||
|
||||
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
|
||||
/// and invalidated
|
||||
virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0;
|
||||
virtual void FlushAndInvalidateRegion(CacheAddr addr, u64 size) = 0;
|
||||
|
||||
/// Attempt to use a faster method to perform a surface copy
|
||||
virtual bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
|
||||
@@ -63,7 +63,7 @@ public:
|
||||
}
|
||||
|
||||
/// Increase/decrease the number of object in pages touching the specified region
|
||||
virtual void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) {}
|
||||
virtual void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {}
|
||||
|
||||
/// Initialize disk cached resources for the game being emulated
|
||||
virtual void LoadDiskResources(const std::atomic_bool& stop_loading = false,
|
||||
|
||||
@@ -13,24 +13,28 @@
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
CachedBufferEntry::CachedBufferEntry(VAddr cpu_addr, std::size_t size, GLintptr offset,
|
||||
std::size_t alignment, u8* host_ptr)
|
||||
: cpu_addr{cpu_addr}, size{size}, offset{offset}, alignment{alignment}, RasterizerCacheObject{
|
||||
host_ptr} {}
|
||||
|
||||
OGLBufferCache::OGLBufferCache(RasterizerOpenGL& rasterizer, std::size_t size)
|
||||
: RasterizerCache{rasterizer}, stream_buffer(size, true) {}
|
||||
|
||||
GLintptr OGLBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size,
|
||||
std::size_t alignment, bool cache) {
|
||||
auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager();
|
||||
const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
|
||||
ASSERT_MSG(cpu_addr, "Invalid GPU address");
|
||||
|
||||
// Cache management is a big overhead, so only cache entries with a given size.
|
||||
// TODO: Figure out which size is the best for given games.
|
||||
cache &= size >= 2048;
|
||||
|
||||
const auto& host_ptr{memory_manager.GetPointer(gpu_addr)};
|
||||
if (cache) {
|
||||
auto entry = TryGet(*cpu_addr);
|
||||
auto entry = TryGet(host_ptr);
|
||||
if (entry) {
|
||||
if (entry->size >= size && entry->alignment == alignment) {
|
||||
return entry->offset;
|
||||
if (entry->GetSize() >= size && entry->GetAlignment() == alignment) {
|
||||
return entry->GetOffset();
|
||||
}
|
||||
Unregister(entry);
|
||||
}
|
||||
@@ -39,17 +43,17 @@ GLintptr OGLBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size
|
||||
AlignBuffer(alignment);
|
||||
const GLintptr uploaded_offset = buffer_offset;
|
||||
|
||||
Memory::ReadBlock(*cpu_addr, buffer_ptr, size);
|
||||
if (!host_ptr) {
|
||||
return uploaded_offset;
|
||||
}
|
||||
|
||||
std::memcpy(buffer_ptr, host_ptr, size);
|
||||
buffer_ptr += size;
|
||||
buffer_offset += size;
|
||||
|
||||
if (cache) {
|
||||
auto entry = std::make_shared<CachedBufferEntry>();
|
||||
entry->offset = uploaded_offset;
|
||||
entry->size = size;
|
||||
entry->alignment = alignment;
|
||||
entry->addr = *cpu_addr;
|
||||
auto entry = std::make_shared<CachedBufferEntry>(
|
||||
*memory_manager.GpuToCpuAddress(gpu_addr), size, uploaded_offset, alignment, host_ptr);
|
||||
Register(entry);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,22 +17,39 @@ namespace OpenGL {
|
||||
|
||||
class RasterizerOpenGL;
|
||||
|
||||
struct CachedBufferEntry final : public RasterizerCacheObject {
|
||||
VAddr GetAddr() const override {
|
||||
return addr;
|
||||
class CachedBufferEntry final : public RasterizerCacheObject {
|
||||
public:
|
||||
explicit CachedBufferEntry(VAddr cpu_addr, std::size_t size, GLintptr offset,
|
||||
std::size_t alignment, u8* host_ptr);
|
||||
|
||||
VAddr GetCpuAddr() const override {
|
||||
return cpu_addr;
|
||||
}
|
||||
|
||||
std::size_t GetSizeInBytes() const override {
|
||||
return size;
|
||||
}
|
||||
|
||||
std::size_t GetSize() const {
|
||||
return size;
|
||||
}
|
||||
|
||||
GLintptr GetOffset() const {
|
||||
return offset;
|
||||
}
|
||||
|
||||
std::size_t GetAlignment() const {
|
||||
return alignment;
|
||||
}
|
||||
|
||||
// We do not have to flush this cache as things in it are never modified by us.
|
||||
void Flush() override {}
|
||||
|
||||
VAddr addr;
|
||||
std::size_t size;
|
||||
GLintptr offset;
|
||||
std::size_t alignment;
|
||||
private:
|
||||
VAddr cpu_addr{};
|
||||
std::size_t size{};
|
||||
GLintptr offset{};
|
||||
std::size_t alignment{};
|
||||
};
|
||||
|
||||
class OGLBufferCache final : public RasterizerCache<std::shared_ptr<CachedBufferEntry>> {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/memory.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"
|
||||
@@ -15,12 +14,13 @@
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
CachedGlobalRegion::CachedGlobalRegion(VAddr addr, u32 size) : addr{addr}, size{size} {
|
||||
CachedGlobalRegion::CachedGlobalRegion(VAddr cpu_addr, u32 size, u8* host_ptr)
|
||||
: cpu_addr{cpu_addr}, size{size}, RasterizerCacheObject{host_ptr} {
|
||||
buffer.Create();
|
||||
// Bind and unbind the buffer so it gets allocated by the driver
|
||||
glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer.handle);
|
||||
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
|
||||
LabelGLObject(GL_BUFFER, buffer.handle, addr, "GlobalMemory");
|
||||
LabelGLObject(GL_BUFFER, buffer.handle, cpu_addr, "GlobalMemory");
|
||||
}
|
||||
|
||||
void CachedGlobalRegion::Reload(u32 size_) {
|
||||
@@ -35,10 +35,10 @@ void CachedGlobalRegion::Reload(u32 size_) {
|
||||
|
||||
// TODO(Rodrigo): Get rid of Memory::GetPointer with a staging buffer
|
||||
glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer.handle);
|
||||
glBufferData(GL_SHADER_STORAGE_BUFFER, size, Memory::GetPointer(addr), GL_DYNAMIC_DRAW);
|
||||
glBufferData(GL_SHADER_STORAGE_BUFFER, size, GetHostPtr(), GL_DYNAMIC_DRAW);
|
||||
}
|
||||
|
||||
GlobalRegion GlobalRegionCacheOpenGL::TryGetReservedGlobalRegion(VAddr addr, u32 size) const {
|
||||
GlobalRegion GlobalRegionCacheOpenGL::TryGetReservedGlobalRegion(CacheAddr addr, u32 size) const {
|
||||
const auto search{reserve.find(addr)};
|
||||
if (search == reserve.end()) {
|
||||
return {};
|
||||
@@ -46,19 +46,22 @@ GlobalRegion GlobalRegionCacheOpenGL::TryGetReservedGlobalRegion(VAddr addr, u32
|
||||
return search->second;
|
||||
}
|
||||
|
||||
GlobalRegion GlobalRegionCacheOpenGL::GetUncachedGlobalRegion(VAddr addr, u32 size) {
|
||||
GlobalRegion region{TryGetReservedGlobalRegion(addr, size)};
|
||||
GlobalRegion GlobalRegionCacheOpenGL::GetUncachedGlobalRegion(Tegra::GPUVAddr addr, u32 size,
|
||||
u8* host_ptr) {
|
||||
GlobalRegion region{TryGetReservedGlobalRegion(ToCacheAddr(host_ptr), size)};
|
||||
if (!region) {
|
||||
// No reserved surface available, create a new one and reserve it
|
||||
region = std::make_shared<CachedGlobalRegion>(addr, size);
|
||||
auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
|
||||
const auto cpu_addr = *memory_manager.GpuToCpuAddress(addr);
|
||||
region = std::make_shared<CachedGlobalRegion>(cpu_addr, size, host_ptr);
|
||||
ReserveGlobalRegion(region);
|
||||
}
|
||||
region->Reload(size);
|
||||
return region;
|
||||
}
|
||||
|
||||
void GlobalRegionCacheOpenGL::ReserveGlobalRegion(const GlobalRegion& region) {
|
||||
reserve[region->GetAddr()] = region;
|
||||
void GlobalRegionCacheOpenGL::ReserveGlobalRegion(GlobalRegion region) {
|
||||
reserve.insert_or_assign(region->GetCacheAddr(), std::move(region));
|
||||
}
|
||||
|
||||
GlobalRegionCacheOpenGL::GlobalRegionCacheOpenGL(RasterizerOpenGL& rasterizer)
|
||||
@@ -69,22 +72,20 @@ GlobalRegion GlobalRegionCacheOpenGL::GetGlobalRegion(
|
||||
Tegra::Engines::Maxwell3D::Regs::ShaderStage stage) {
|
||||
|
||||
auto& gpu{Core::System::GetInstance().GPU()};
|
||||
const auto cbufs = gpu.Maxwell3D().state.shader_stages[static_cast<u64>(stage)];
|
||||
const auto cbuf_addr = gpu.MemoryManager().GpuToCpuAddress(
|
||||
cbufs.const_buffers[global_region.GetCbufIndex()].address + global_region.GetCbufOffset());
|
||||
ASSERT(cbuf_addr);
|
||||
|
||||
const auto actual_addr_gpu = Memory::Read64(*cbuf_addr);
|
||||
const auto size = Memory::Read32(*cbuf_addr + 8);
|
||||
const auto actual_addr = gpu.MemoryManager().GpuToCpuAddress(actual_addr_gpu);
|
||||
ASSERT(actual_addr);
|
||||
auto& memory_manager{gpu.MemoryManager()};
|
||||
const auto cbufs{gpu.Maxwell3D().state.shader_stages[static_cast<u64>(stage)]};
|
||||
const auto addr{cbufs.const_buffers[global_region.GetCbufIndex()].address +
|
||||
global_region.GetCbufOffset()};
|
||||
const auto actual_addr{memory_manager.Read64(addr)};
|
||||
const auto size{memory_manager.Read32(addr + 8)};
|
||||
|
||||
// Look up global region in the cache based on address
|
||||
GlobalRegion region = TryGet(*actual_addr);
|
||||
const auto& host_ptr{memory_manager.GetPointer(actual_addr)};
|
||||
GlobalRegion region{TryGet(host_ptr)};
|
||||
|
||||
if (!region) {
|
||||
// No global region found - create a new one
|
||||
region = GetUncachedGlobalRegion(*actual_addr, size);
|
||||
region = GetUncachedGlobalRegion(actual_addr, size, host_ptr);
|
||||
Register(region);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,15 +27,13 @@ using GlobalRegion = std::shared_ptr<CachedGlobalRegion>;
|
||||
|
||||
class CachedGlobalRegion final : public RasterizerCacheObject {
|
||||
public:
|
||||
explicit CachedGlobalRegion(VAddr addr, u32 size);
|
||||
explicit CachedGlobalRegion(VAddr cpu_addr, u32 size, u8* host_ptr);
|
||||
|
||||
/// Gets the address of the shader in guest memory, required for cache management
|
||||
VAddr GetAddr() const {
|
||||
return addr;
|
||||
VAddr GetCpuAddr() const override {
|
||||
return cpu_addr;
|
||||
}
|
||||
|
||||
/// Gets the size of the shader in guest memory, required for cache management
|
||||
std::size_t GetSizeInBytes() const {
|
||||
std::size_t GetSizeInBytes() const override {
|
||||
return size;
|
||||
}
|
||||
|
||||
@@ -53,9 +51,8 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
VAddr addr{};
|
||||
VAddr cpu_addr{};
|
||||
u32 size{};
|
||||
|
||||
OGLBuffer buffer;
|
||||
};
|
||||
|
||||
@@ -68,11 +65,11 @@ public:
|
||||
Tegra::Engines::Maxwell3D::Regs::ShaderStage stage);
|
||||
|
||||
private:
|
||||
GlobalRegion TryGetReservedGlobalRegion(VAddr addr, u32 size) const;
|
||||
GlobalRegion GetUncachedGlobalRegion(VAddr addr, u32 size);
|
||||
void ReserveGlobalRegion(const GlobalRegion& region);
|
||||
GlobalRegion TryGetReservedGlobalRegion(CacheAddr addr, u32 size) const;
|
||||
GlobalRegion GetUncachedGlobalRegion(Tegra::GPUVAddr addr, u32 size, u8* host_ptr);
|
||||
void ReserveGlobalRegion(GlobalRegion region);
|
||||
|
||||
std::unordered_map<VAddr, GlobalRegion> reserve;
|
||||
std::unordered_map<CacheAddr, GlobalRegion> reserve;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
@@ -46,10 +46,7 @@ GLintptr PrimitiveAssembler::MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size
|
||||
auto [dst_pointer, index_offset] = buffer_cache.ReserveMemory(map_size);
|
||||
|
||||
auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager();
|
||||
const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
|
||||
ASSERT_MSG(cpu_addr, "Invalid GPU address");
|
||||
|
||||
const u8* source{Memory::GetPointer(*cpu_addr)};
|
||||
const u8* source{memory_manager.GetPointer(gpu_addr)};
|
||||
|
||||
for (u32 primitive = 0; primitive < count / 4; ++primitive) {
|
||||
for (std::size_t i = 0; i < TRIANGLES_PER_QUAD; ++i) {
|
||||
@@ -64,4 +61,4 @@ GLintptr PrimitiveAssembler::MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size
|
||||
return index_offset;
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
||||
} // namespace OpenGL
|
||||
|
||||
@@ -102,8 +102,9 @@ struct FramebufferCacheKey {
|
||||
|
||||
RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, Core::System& system,
|
||||
ScreenInfo& info)
|
||||
: res_cache{*this}, shader_cache{*this, system}, global_cache{*this}, emu_window{window},
|
||||
screen_info{info}, buffer_cache(*this, STREAM_BUFFER_SIZE) {
|
||||
: res_cache{*this}, shader_cache{*this, system}, global_cache{*this},
|
||||
emu_window{window}, system{system}, screen_info{info},
|
||||
buffer_cache(*this, STREAM_BUFFER_SIZE) {
|
||||
// Create sampler objects
|
||||
for (std::size_t i = 0; i < texture_samplers.size(); ++i) {
|
||||
texture_samplers[i].Create();
|
||||
@@ -138,7 +139,7 @@ void RasterizerOpenGL::CheckExtensions() {
|
||||
}
|
||||
|
||||
GLuint RasterizerOpenGL::SetupVertexFormat() {
|
||||
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
const auto& regs = gpu.regs;
|
||||
|
||||
if (!gpu.dirty_flags.vertex_attrib_format) {
|
||||
@@ -207,7 +208,7 @@ GLuint RasterizerOpenGL::SetupVertexFormat() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SetupVertexBuffer(GLuint vao) {
|
||||
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
const auto& regs = gpu.regs;
|
||||
|
||||
if (gpu.dirty_flags.vertex_array.none())
|
||||
@@ -248,7 +249,7 @@ void RasterizerOpenGL::SetupVertexBuffer(GLuint vao) {
|
||||
}
|
||||
|
||||
DrawParameters RasterizerOpenGL::SetupDraw() {
|
||||
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
const auto& gpu = system.GPU().Maxwell3D();
|
||||
const auto& regs = gpu.regs;
|
||||
const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
|
||||
|
||||
@@ -297,7 +298,7 @@ DrawParameters RasterizerOpenGL::SetupDraw() {
|
||||
|
||||
void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
|
||||
MICROPROFILE_SCOPE(OpenGL_Shader);
|
||||
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
|
||||
BaseBindings base_bindings;
|
||||
std::array<bool, Maxwell::NumClipDistances> clip_distances{};
|
||||
@@ -413,7 +414,7 @@ void RasterizerOpenGL::SetupCachedFramebuffer(const FramebufferCacheKey& fbkey,
|
||||
}
|
||||
|
||||
std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
|
||||
std::size_t size = 0;
|
||||
for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) {
|
||||
@@ -431,7 +432,7 @@ std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
|
||||
}
|
||||
|
||||
std::size_t RasterizerOpenGL::CalculateIndexBufferSize() const {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
|
||||
return static_cast<std::size_t>(regs.index_array.count) *
|
||||
static_cast<std::size_t>(regs.index_array.FormatSizeInBytes());
|
||||
@@ -448,7 +449,7 @@ static constexpr auto RangeFromInterval(Map& map, const Interval& interval) {
|
||||
return boost::make_iterator_range(map.equal_range(interval));
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) {
|
||||
void RasterizerOpenGL::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
|
||||
const u64 page_start{addr >> Memory::PAGE_BITS};
|
||||
const u64 page_end{(addr + size + Memory::PAGE_SIZE - 1) >> Memory::PAGE_BITS};
|
||||
|
||||
@@ -487,7 +488,7 @@ std::pair<bool, bool> RasterizerOpenGL::ConfigureFramebuffers(
|
||||
OpenGLState& current_state, bool using_color_fb, bool using_depth_fb, bool preserve_contents,
|
||||
std::optional<std::size_t> single_color_target) {
|
||||
MICROPROFILE_SCOPE(OpenGL_Framebuffer);
|
||||
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
const auto& regs = gpu.regs;
|
||||
|
||||
const FramebufferConfigState fb_config_state{using_color_fb, using_depth_fb, preserve_contents,
|
||||
@@ -581,7 +582,7 @@ void RasterizerOpenGL::Clear() {
|
||||
const auto prev_state{state};
|
||||
SCOPE_EXIT({ prev_state.Apply(); });
|
||||
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
bool use_color{};
|
||||
bool use_depth{};
|
||||
bool use_stencil{};
|
||||
@@ -672,7 +673,7 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_Drawing);
|
||||
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
const auto& regs = gpu.regs;
|
||||
|
||||
ConfigureFramebuffers(state);
|
||||
@@ -746,20 +747,26 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
|
||||
void RasterizerOpenGL::FlushAll() {}
|
||||
|
||||
void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {
|
||||
void RasterizerOpenGL::FlushRegion(CacheAddr addr, u64 size) {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
if (!addr || !size) {
|
||||
return;
|
||||
}
|
||||
res_cache.FlushRegion(addr, size);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
|
||||
void RasterizerOpenGL::InvalidateRegion(CacheAddr addr, u64 size) {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
if (!addr || !size) {
|
||||
return;
|
||||
}
|
||||
res_cache.InvalidateRegion(addr, size);
|
||||
shader_cache.InvalidateRegion(addr, size);
|
||||
global_cache.InvalidateRegion(addr, size);
|
||||
buffer_cache.InvalidateRegion(addr, size);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) {
|
||||
void RasterizerOpenGL::FlushAndInvalidateRegion(CacheAddr addr, u64 size) {
|
||||
FlushRegion(addr, size);
|
||||
InvalidateRegion(addr, size);
|
||||
}
|
||||
@@ -781,7 +788,7 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config,
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
|
||||
const auto& surface{res_cache.TryFindFramebufferSurface(framebuffer_addr)};
|
||||
const auto& surface{res_cache.TryFindFramebufferSurface(Memory::GetPointer(framebuffer_addr))};
|
||||
if (!surface) {
|
||||
return {};
|
||||
}
|
||||
@@ -892,7 +899,7 @@ void RasterizerOpenGL::SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::Shader
|
||||
const Shader& shader, GLuint program_handle,
|
||||
BaseBindings base_bindings) {
|
||||
MICROPROFILE_SCOPE(OpenGL_UBO);
|
||||
const auto& gpu = Core::System::GetInstance().GPU();
|
||||
const auto& gpu = system.GPU();
|
||||
const auto& maxwell3d = gpu.Maxwell3D();
|
||||
const auto& shader_stage = maxwell3d.state.shader_stages[static_cast<std::size_t>(stage)];
|
||||
const auto& entries = shader->GetShaderEntries().const_buffers;
|
||||
@@ -971,7 +978,7 @@ void RasterizerOpenGL::SetupGlobalRegions(Tegra::Engines::Maxwell3D::Regs::Shade
|
||||
void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& shader,
|
||||
GLuint program_handle, BaseBindings base_bindings) {
|
||||
MICROPROFILE_SCOPE(OpenGL_Texture);
|
||||
const auto& gpu = Core::System::GetInstance().GPU();
|
||||
const auto& gpu = system.GPU();
|
||||
const auto& maxwell3d = gpu.Maxwell3D();
|
||||
const auto& entries = shader->GetShaderEntries().samplers;
|
||||
|
||||
@@ -998,7 +1005,7 @@ void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& s
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
const bool geometry_shaders_enabled =
|
||||
regs.IsShaderConfigEnabled(static_cast<size_t>(Maxwell::ShaderProgram::Geometry));
|
||||
const std::size_t viewport_count =
|
||||
@@ -1021,7 +1028,7 @@ void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) {
|
||||
void RasterizerOpenGL::SyncClipEnabled(
|
||||
const std::array<bool, Maxwell::Regs::NumClipDistances>& clip_mask) {
|
||||
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
const std::array<bool, Maxwell::Regs::NumClipDistances> reg_state{
|
||||
regs.clip_distance_enabled.c0 != 0, regs.clip_distance_enabled.c1 != 0,
|
||||
regs.clip_distance_enabled.c2 != 0, regs.clip_distance_enabled.c3 != 0,
|
||||
@@ -1038,7 +1045,7 @@ void RasterizerOpenGL::SyncClipCoef() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncCullMode() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
|
||||
state.cull.enabled = regs.cull.enabled != 0;
|
||||
|
||||
@@ -1062,14 +1069,14 @@ void RasterizerOpenGL::SyncCullMode() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncPrimitiveRestart() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
|
||||
state.primitive_restart.enabled = regs.primitive_restart.enabled;
|
||||
state.primitive_restart.index = regs.primitive_restart.index;
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncDepthTestState() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
|
||||
state.depth.test_enabled = regs.depth_test_enable != 0;
|
||||
state.depth.write_mask = regs.depth_write_enabled ? GL_TRUE : GL_FALSE;
|
||||
@@ -1081,7 +1088,7 @@ void RasterizerOpenGL::SyncDepthTestState() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncStencilTestState() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
state.stencil.test_enabled = regs.stencil_enable != 0;
|
||||
|
||||
if (!regs.stencil_enable) {
|
||||
@@ -1115,7 +1122,7 @@ void RasterizerOpenGL::SyncStencilTestState() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncColorMask() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
const std::size_t count =
|
||||
regs.independent_blend_enable ? Tegra::Engines::Maxwell3D::Regs::NumRenderTargets : 1;
|
||||
for (std::size_t i = 0; i < count; i++) {
|
||||
@@ -1129,18 +1136,18 @@ void RasterizerOpenGL::SyncColorMask() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncMultiSampleState() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
state.multisample_control.alpha_to_coverage = regs.multisample_control.alpha_to_coverage != 0;
|
||||
state.multisample_control.alpha_to_one = regs.multisample_control.alpha_to_one != 0;
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncFragmentColorClampState() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
state.fragment_color_clamp.enabled = regs.frag_color_clamp != 0;
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncBlendState() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
|
||||
state.blend_color.red = regs.blend_color.r;
|
||||
state.blend_color.green = regs.blend_color.g;
|
||||
@@ -1182,7 +1189,7 @@ void RasterizerOpenGL::SyncBlendState() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncLogicOpState() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
|
||||
state.logic_op.enabled = regs.logic_op.enable != 0;
|
||||
|
||||
@@ -1196,7 +1203,7 @@ void RasterizerOpenGL::SyncLogicOpState() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncScissorTest(OpenGLState& current_state) {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
const bool geometry_shaders_enabled =
|
||||
regs.IsShaderConfigEnabled(static_cast<size_t>(Maxwell::ShaderProgram::Geometry));
|
||||
const std::size_t viewport_count =
|
||||
@@ -1218,17 +1225,17 @@ void RasterizerOpenGL::SyncScissorTest(OpenGLState& current_state) {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncTransformFeedback() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
UNIMPLEMENTED_IF_MSG(regs.tfb_enabled != 0, "Transform feedbacks are not implemented");
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncPointState() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
state.point.size = regs.point_size;
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncPolygonOffset() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
state.polygon_offset.fill_enable = regs.polygon_offset_fill_enable != 0;
|
||||
state.polygon_offset.line_enable = regs.polygon_offset_line_enable != 0;
|
||||
state.polygon_offset.point_enable = regs.polygon_offset_point_enable != 0;
|
||||
@@ -1238,7 +1245,7 @@ void RasterizerOpenGL::SyncPolygonOffset() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::CheckAlphaTests() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
UNIMPLEMENTED_IF_MSG(regs.alpha_test_enabled != 0 && regs.rt_control.count > 1,
|
||||
"Alpha Testing is enabled with more than one rendertarget");
|
||||
}
|
||||
|
||||
@@ -57,9 +57,9 @@ public:
|
||||
void DrawArrays() override;
|
||||
void Clear() override;
|
||||
void FlushAll() override;
|
||||
void FlushRegion(VAddr addr, u64 size) override;
|
||||
void InvalidateRegion(VAddr addr, u64 size) override;
|
||||
void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
|
||||
void FlushRegion(CacheAddr addr, u64 size) override;
|
||||
void InvalidateRegion(CacheAddr addr, u64 size) override;
|
||||
void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override;
|
||||
bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& dst,
|
||||
const Common::Rectangle<u32>& src_rect,
|
||||
@@ -67,7 +67,7 @@ public:
|
||||
bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
|
||||
u32 pixel_stride) override;
|
||||
bool AccelerateDrawBatch(bool is_indexed) override;
|
||||
void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) override;
|
||||
void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) override;
|
||||
void LoadDiskResources(const std::atomic_bool& stop_loading,
|
||||
const VideoCore::DiskResourceLoadCallback& callback) override;
|
||||
|
||||
@@ -215,6 +215,7 @@ private:
|
||||
GlobalRegionCacheOpenGL global_cache;
|
||||
|
||||
Core::Frontend::EmuWindow& emu_window;
|
||||
Core::System& system;
|
||||
|
||||
ScreenInfo& screen_info;
|
||||
|
||||
|
||||
@@ -57,10 +57,9 @@ static void ApplyTextureDefaults(GLuint texture, u32 max_mip_level) {
|
||||
|
||||
void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) {
|
||||
auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
|
||||
const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr_)};
|
||||
|
||||
addr = cpu_addr ? *cpu_addr : 0;
|
||||
gpu_addr = gpu_addr_;
|
||||
host_ptr = memory_manager.GetPointer(gpu_addr_);
|
||||
size_in_bytes = SizeInBytesRaw();
|
||||
|
||||
if (IsPixelFormatASTC(pixel_format)) {
|
||||
@@ -446,7 +445,7 @@ void SwizzleFunc(const MortonSwizzleMode& mode, const SurfaceParams& params,
|
||||
MortonSwizzle(mode, params.pixel_format, params.MipWidth(mip_level),
|
||||
params.MipBlockHeight(mip_level), params.MipHeight(mip_level),
|
||||
params.MipBlockDepth(mip_level), 1, params.tile_width_spacing,
|
||||
gl_buffer.data() + offset_gl, gl_size, params.addr + offset);
|
||||
gl_buffer.data() + offset_gl, params.host_ptr + offset);
|
||||
offset += layer_size;
|
||||
offset_gl += gl_size;
|
||||
}
|
||||
@@ -455,7 +454,7 @@ void SwizzleFunc(const MortonSwizzleMode& mode, const SurfaceParams& params,
|
||||
MortonSwizzle(mode, params.pixel_format, params.MipWidth(mip_level),
|
||||
params.MipBlockHeight(mip_level), params.MipHeight(mip_level),
|
||||
params.MipBlockDepth(mip_level), depth, params.tile_width_spacing,
|
||||
gl_buffer.data(), gl_buffer.size(), params.addr + offset);
|
||||
gl_buffer.data(), params.host_ptr + offset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,9 +512,9 @@ void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surfac
|
||||
"reinterpretation but the texture is tiled.");
|
||||
}
|
||||
const std::size_t remaining_size = dst_params.size_in_bytes - src_params.size_in_bytes;
|
||||
|
||||
auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
|
||||
glBufferSubData(GL_PIXEL_PACK_BUFFER, src_params.size_in_bytes, remaining_size,
|
||||
Memory::GetPointer(dst_params.addr + src_params.size_in_bytes));
|
||||
memory_manager.GetPointer(dst_params.gpu_addr + src_params.size_in_bytes));
|
||||
}
|
||||
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
@@ -563,8 +562,8 @@ void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surfac
|
||||
}
|
||||
|
||||
CachedSurface::CachedSurface(const SurfaceParams& params)
|
||||
: params(params), gl_target(SurfaceTargetToGL(params.target)),
|
||||
cached_size_in_bytes(params.size_in_bytes) {
|
||||
: params{params}, gl_target{SurfaceTargetToGL(params.target)},
|
||||
cached_size_in_bytes{params.size_in_bytes}, RasterizerCacheObject{params.host_ptr} {
|
||||
texture.Create(gl_target);
|
||||
|
||||
// TODO(Rodrigo): Using params.GetRect() returns a different size than using its Mip*(0)
|
||||
@@ -603,7 +602,7 @@ CachedSurface::CachedSurface(const SurfaceParams& params)
|
||||
|
||||
ApplyTextureDefaults(texture.handle, params.max_mip_level);
|
||||
|
||||
OpenGL::LabelGLObject(GL_TEXTURE, texture.handle, params.addr, params.IdentityString());
|
||||
OpenGL::LabelGLObject(GL_TEXTURE, texture.handle, params.gpu_addr, params.IdentityString());
|
||||
|
||||
// Clamp size to mapped GPU memory region
|
||||
// TODO(bunnei): Super Mario Odyssey maps a 0x40000 byte region and then uses it for a 0x80000
|
||||
@@ -616,6 +615,8 @@ CachedSurface::CachedSurface(const SurfaceParams& params)
|
||||
LOG_ERROR(HW_GPU, "Surface size {} exceeds region size {}", params.size_in_bytes, max_size);
|
||||
cached_size_in_bytes = max_size;
|
||||
}
|
||||
|
||||
cpu_addr = *memory_manager.GpuToCpuAddress(params.gpu_addr);
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 192, 64));
|
||||
@@ -633,10 +634,9 @@ void CachedSurface::LoadGLBuffer() {
|
||||
const u32 bpp = params.GetFormatBpp() / 8;
|
||||
const u32 copy_size = params.width * bpp;
|
||||
if (params.pitch == copy_size) {
|
||||
std::memcpy(gl_buffer[0].data(), Memory::GetPointer(params.addr),
|
||||
params.size_in_bytes_gl);
|
||||
std::memcpy(gl_buffer[0].data(), params.host_ptr, params.size_in_bytes_gl);
|
||||
} else {
|
||||
const u8* start = Memory::GetPointer(params.addr);
|
||||
const u8* start{params.host_ptr};
|
||||
u8* write_to = gl_buffer[0].data();
|
||||
for (u32 h = params.height; h > 0; h--) {
|
||||
std::memcpy(write_to, start, copy_size);
|
||||
@@ -680,8 +680,6 @@ void CachedSurface::FlushGLBuffer() {
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
Tegra::Texture::ConvertFromHostToGuest(gl_buffer[0].data(), params.pixel_format, params.width,
|
||||
params.height, params.depth, true, true);
|
||||
const u8* const texture_src_data = Memory::GetPointer(params.addr);
|
||||
ASSERT(texture_src_data);
|
||||
if (params.is_tiled) {
|
||||
ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}",
|
||||
params.block_width, static_cast<u32>(params.target));
|
||||
@@ -691,9 +689,9 @@ void CachedSurface::FlushGLBuffer() {
|
||||
const u32 bpp = params.GetFormatBpp() / 8;
|
||||
const u32 copy_size = params.width * bpp;
|
||||
if (params.pitch == copy_size) {
|
||||
std::memcpy(Memory::GetPointer(params.addr), gl_buffer[0].data(), GetSizeInBytes());
|
||||
std::memcpy(params.host_ptr, gl_buffer[0].data(), GetSizeInBytes());
|
||||
} else {
|
||||
u8* start = Memory::GetPointer(params.addr);
|
||||
u8* start{params.host_ptr};
|
||||
const u8* read_to = gl_buffer[0].data();
|
||||
for (u32 h = params.height; h > 0; h--) {
|
||||
std::memcpy(start, read_to, copy_size);
|
||||
@@ -927,12 +925,12 @@ void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) {
|
||||
}
|
||||
|
||||
Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool preserve_contents) {
|
||||
if (params.addr == 0 || params.height * params.width == 0) {
|
||||
if (params.gpu_addr == 0 || params.height * params.width == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Look up surface in the cache based on address
|
||||
Surface surface{TryGet(params.addr)};
|
||||
Surface surface{TryGet(params.host_ptr)};
|
||||
if (surface) {
|
||||
if (surface->GetSurfaceParams().IsCompatibleSurface(params)) {
|
||||
// Use the cached surface as-is unless it's not synced with memory
|
||||
@@ -981,14 +979,16 @@ void RasterizerCacheOpenGL::FastLayeredCopySurface(const Surface& src_surface,
|
||||
const Surface& dst_surface) {
|
||||
const auto& init_params{src_surface->GetSurfaceParams()};
|
||||
const auto& dst_params{dst_surface->GetSurfaceParams()};
|
||||
VAddr address = init_params.addr;
|
||||
const std::size_t layer_size = dst_params.LayerMemorySize();
|
||||
auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
|
||||
Tegra::GPUVAddr address{init_params.gpu_addr};
|
||||
const std::size_t layer_size{dst_params.LayerMemorySize()};
|
||||
for (u32 layer = 0; layer < dst_params.depth; layer++) {
|
||||
for (u32 mipmap = 0; mipmap < dst_params.max_mip_level; mipmap++) {
|
||||
const VAddr sub_address = address + dst_params.GetMipmapLevelOffset(mipmap);
|
||||
const Surface& copy = TryGet(sub_address);
|
||||
if (!copy)
|
||||
const Tegra::GPUVAddr sub_address{address + dst_params.GetMipmapLevelOffset(mipmap)};
|
||||
const Surface& copy{TryGet(memory_manager.GetPointer(sub_address))};
|
||||
if (!copy) {
|
||||
continue;
|
||||
}
|
||||
const auto& src_params{copy->GetSurfaceParams()};
|
||||
const u32 width{std::min(src_params.width, dst_params.MipWidth(mipmap))};
|
||||
const u32 height{std::min(src_params.height, dst_params.MipHeight(mipmap))};
|
||||
@@ -1163,7 +1163,8 @@ void RasterizerCacheOpenGL::AccurateCopySurface(const Surface& src_surface,
|
||||
const auto& dst_params{dst_surface->GetSurfaceParams()};
|
||||
|
||||
// Flush enough memory for both the source and destination surface
|
||||
FlushRegion(src_params.addr, std::max(src_params.MemorySize(), dst_params.MemorySize()));
|
||||
FlushRegion(ToCacheAddr(src_params.host_ptr),
|
||||
std::max(src_params.MemorySize(), dst_params.MemorySize()));
|
||||
|
||||
LoadSurface(dst_surface);
|
||||
}
|
||||
@@ -1215,8 +1216,8 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
|
||||
return new_surface;
|
||||
}
|
||||
|
||||
Surface RasterizerCacheOpenGL::TryFindFramebufferSurface(VAddr addr) const {
|
||||
return TryGet(addr);
|
||||
Surface RasterizerCacheOpenGL::TryFindFramebufferSurface(const u8* host_ptr) const {
|
||||
return TryGet(host_ptr);
|
||||
}
|
||||
|
||||
void RasterizerCacheOpenGL::ReserveSurface(const Surface& surface) {
|
||||
@@ -1243,9 +1244,10 @@ static std::optional<u32> TryFindBestMipMap(std::size_t memory, const SurfacePar
|
||||
return {};
|
||||
}
|
||||
|
||||
static std::optional<u32> TryFindBestLayer(VAddr addr, const SurfaceParams params, u32 mipmap) {
|
||||
const std::size_t size = params.LayerMemorySize();
|
||||
VAddr start = params.addr + params.GetMipmapLevelOffset(mipmap);
|
||||
static std::optional<u32> TryFindBestLayer(Tegra::GPUVAddr addr, const SurfaceParams params,
|
||||
u32 mipmap) {
|
||||
const std::size_t size{params.LayerMemorySize()};
|
||||
Tegra::GPUVAddr start{params.gpu_addr + params.GetMipmapLevelOffset(mipmap)};
|
||||
for (u32 i = 0; i < params.depth; i++) {
|
||||
if (start == addr) {
|
||||
return {i};
|
||||
@@ -1267,7 +1269,7 @@ static bool LayerFitReinterpretSurface(RasterizerCacheOpenGL& cache, const Surfa
|
||||
src_params.height == dst_params.MipHeight(*level) &&
|
||||
src_params.block_height >= dst_params.MipBlockHeight(*level)) {
|
||||
const std::optional<u32> slot =
|
||||
TryFindBestLayer(render_surface->GetAddr(), dst_params, *level);
|
||||
TryFindBestLayer(render_surface->GetSurfaceParams().gpu_addr, dst_params, *level);
|
||||
if (slot.has_value()) {
|
||||
glCopyImageSubData(render_surface->Texture().handle,
|
||||
SurfaceTargetToGL(src_params.target), 0, 0, 0, 0,
|
||||
@@ -1283,8 +1285,8 @@ static bool LayerFitReinterpretSurface(RasterizerCacheOpenGL& cache, const Surfa
|
||||
}
|
||||
|
||||
static bool IsReinterpretInvalid(const Surface render_surface, const Surface blitted_surface) {
|
||||
const VAddr bound1 = blitted_surface->GetAddr() + blitted_surface->GetMemorySize();
|
||||
const VAddr bound2 = render_surface->GetAddr() + render_surface->GetMemorySize();
|
||||
const VAddr bound1 = blitted_surface->GetCpuAddr() + blitted_surface->GetMemorySize();
|
||||
const VAddr bound2 = render_surface->GetCpuAddr() + render_surface->GetMemorySize();
|
||||
if (bound2 > bound1)
|
||||
return true;
|
||||
const auto& dst_params = blitted_surface->GetSurfaceParams();
|
||||
@@ -1327,7 +1329,8 @@ void RasterizerCacheOpenGL::SignalPreDrawCall() {
|
||||
void RasterizerCacheOpenGL::SignalPostDrawCall() {
|
||||
for (u32 i = 0; i < Maxwell::NumRenderTargets; i++) {
|
||||
if (current_color_buffers[i] != nullptr) {
|
||||
Surface intersect = CollideOnReinterpretedSurface(current_color_buffers[i]->GetAddr());
|
||||
Surface intersect =
|
||||
CollideOnReinterpretedSurface(current_color_buffers[i]->GetCacheAddr());
|
||||
if (intersect != nullptr) {
|
||||
PartialReinterpretSurface(current_color_buffers[i], intersect);
|
||||
texception = true;
|
||||
|
||||
@@ -296,7 +296,7 @@ struct SurfaceParams {
|
||||
bool is_array;
|
||||
bool srgb_conversion;
|
||||
// Parameters used for caching
|
||||
VAddr addr;
|
||||
u8* host_ptr;
|
||||
Tegra::GPUVAddr gpu_addr;
|
||||
std::size_t size_in_bytes;
|
||||
std::size_t size_in_bytes_gl;
|
||||
@@ -345,10 +345,10 @@ class RasterizerOpenGL;
|
||||
|
||||
class CachedSurface final : public RasterizerCacheObject {
|
||||
public:
|
||||
CachedSurface(const SurfaceParams& params);
|
||||
explicit CachedSurface(const SurfaceParams& params);
|
||||
|
||||
VAddr GetAddr() const override {
|
||||
return params.addr;
|
||||
VAddr GetCpuAddr() const override {
|
||||
return cpu_addr;
|
||||
}
|
||||
|
||||
std::size_t GetSizeInBytes() const override {
|
||||
@@ -432,6 +432,7 @@ private:
|
||||
std::size_t memory_size;
|
||||
bool reinterpreted = false;
|
||||
bool must_reload = false;
|
||||
VAddr cpu_addr{};
|
||||
};
|
||||
|
||||
class RasterizerCacheOpenGL final : public RasterizerCache<Surface> {
|
||||
@@ -449,7 +450,7 @@ public:
|
||||
Surface GetColorBufferSurface(std::size_t index, bool preserve_contents);
|
||||
|
||||
/// Tries to find a framebuffer using on the provided CPU address
|
||||
Surface TryFindFramebufferSurface(VAddr addr) const;
|
||||
Surface TryFindFramebufferSurface(const u8* host_ptr) const;
|
||||
|
||||
/// Copies the contents of one surface to another
|
||||
void FermiCopySurface(const Tegra::Engines::Fermi2D::Regs::Surface& src_config,
|
||||
@@ -506,12 +507,12 @@ private:
|
||||
std::array<Surface, Maxwell::NumRenderTargets> current_color_buffers;
|
||||
Surface last_depth_buffer;
|
||||
|
||||
using SurfaceIntervalCache = boost::icl::interval_map<VAddr, Surface>;
|
||||
using SurfaceIntervalCache = boost::icl::interval_map<CacheAddr, Surface>;
|
||||
using SurfaceInterval = typename SurfaceIntervalCache::interval_type;
|
||||
|
||||
static auto GetReinterpretInterval(const Surface& object) {
|
||||
return SurfaceInterval::right_open(object->GetAddr() + 1,
|
||||
object->GetAddr() + object->GetMemorySize() - 1);
|
||||
return SurfaceInterval::right_open(object->GetCacheAddr() + 1,
|
||||
object->GetCacheAddr() + object->GetMemorySize() - 1);
|
||||
}
|
||||
|
||||
// Reinterpreted surfaces are very fragil as the game may keep rendering into them.
|
||||
@@ -523,7 +524,7 @@ private:
|
||||
reinterpret_surface->MarkReinterpreted();
|
||||
}
|
||||
|
||||
Surface CollideOnReinterpretedSurface(VAddr addr) const {
|
||||
Surface CollideOnReinterpretedSurface(CacheAddr addr) const {
|
||||
const SurfaceInterval interval{addr};
|
||||
for (auto& pair :
|
||||
boost::make_iterator_range(reinterpreted_surfaces.equal_range(interval))) {
|
||||
|
||||
@@ -32,19 +32,16 @@ struct UnspecializedShader {
|
||||
namespace {
|
||||
|
||||
/// Gets the address for the specified shader stage program
|
||||
VAddr GetShaderAddress(Maxwell::ShaderProgram program) {
|
||||
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
const auto& shader_config = gpu.regs.shader_config[static_cast<std::size_t>(program)];
|
||||
const auto address = gpu.memory_manager.GpuToCpuAddress(gpu.regs.code_address.CodeAddress() +
|
||||
shader_config.offset);
|
||||
ASSERT_MSG(address, "Invalid GPU address");
|
||||
return *address;
|
||||
Tegra::GPUVAddr GetShaderAddress(Maxwell::ShaderProgram program) {
|
||||
const auto& gpu{Core::System::GetInstance().GPU().Maxwell3D()};
|
||||
const auto& shader_config{gpu.regs.shader_config[static_cast<std::size_t>(program)]};
|
||||
return gpu.regs.code_address.CodeAddress() + shader_config.offset;
|
||||
}
|
||||
|
||||
/// Gets the shader program code from memory for the specified address
|
||||
ProgramCode GetShaderCode(VAddr addr) {
|
||||
ProgramCode GetShaderCode(const u8* host_ptr) {
|
||||
ProgramCode program_code(VideoCommon::Shader::MAX_PROGRAM_LENGTH);
|
||||
Memory::ReadBlock(addr, program_code.data(), program_code.size() * sizeof(u64));
|
||||
std::memcpy(program_code.data(), host_ptr, program_code.size() * sizeof(u64));
|
||||
return program_code;
|
||||
}
|
||||
|
||||
@@ -214,12 +211,13 @@ std::set<GLenum> GetSupportedFormats() {
|
||||
|
||||
} // namespace
|
||||
|
||||
CachedShader::CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type,
|
||||
ShaderDiskCacheOpenGL& disk_cache,
|
||||
CachedShader::CachedShader(VAddr cpu_addr, u64 unique_identifier,
|
||||
Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache,
|
||||
const PrecompiledPrograms& precompiled_programs,
|
||||
ProgramCode&& program_code, ProgramCode&& program_code_b)
|
||||
: addr{addr}, unique_identifier{unique_identifier}, program_type{program_type},
|
||||
disk_cache{disk_cache}, precompiled_programs{precompiled_programs} {
|
||||
ProgramCode&& program_code, ProgramCode&& program_code_b, u8* host_ptr)
|
||||
: host_ptr{host_ptr}, cpu_addr{cpu_addr}, unique_identifier{unique_identifier},
|
||||
program_type{program_type}, disk_cache{disk_cache},
|
||||
precompiled_programs{precompiled_programs}, RasterizerCacheObject{host_ptr} {
|
||||
|
||||
const std::size_t code_size = CalculateProgramSize(program_code);
|
||||
const std::size_t code_size_b =
|
||||
@@ -243,12 +241,13 @@ CachedShader::CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderPro
|
||||
disk_cache.SaveRaw(raw);
|
||||
}
|
||||
|
||||
CachedShader::CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type,
|
||||
ShaderDiskCacheOpenGL& disk_cache,
|
||||
CachedShader::CachedShader(VAddr cpu_addr, u64 unique_identifier,
|
||||
Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache,
|
||||
const PrecompiledPrograms& precompiled_programs,
|
||||
GLShader::ProgramResult result)
|
||||
: addr{addr}, unique_identifier{unique_identifier}, program_type{program_type},
|
||||
disk_cache{disk_cache}, precompiled_programs{precompiled_programs} {
|
||||
GLShader::ProgramResult result, u8* host_ptr)
|
||||
: cpu_addr{cpu_addr}, unique_identifier{unique_identifier}, program_type{program_type},
|
||||
disk_cache{disk_cache}, precompiled_programs{precompiled_programs}, RasterizerCacheObject{
|
||||
host_ptr} {
|
||||
|
||||
code = std::move(result.first);
|
||||
entries = result.second;
|
||||
@@ -271,7 +270,7 @@ std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(GLenum primitive
|
||||
disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings));
|
||||
}
|
||||
|
||||
LabelGLObject(GL_PROGRAM, program->handle, addr);
|
||||
LabelGLObject(GL_PROGRAM, program->handle, cpu_addr);
|
||||
}
|
||||
|
||||
handle = program->handle;
|
||||
@@ -323,7 +322,7 @@ GLuint CachedShader::LazyGeometryProgram(CachedProgram& target_program, BaseBind
|
||||
disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings));
|
||||
}
|
||||
|
||||
LabelGLObject(GL_PROGRAM, target_program->handle, addr, debug_name);
|
||||
LabelGLObject(GL_PROGRAM, target_program->handle, cpu_addr, debug_name);
|
||||
|
||||
return target_program->handle;
|
||||
};
|
||||
@@ -486,29 +485,32 @@ Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
|
||||
return last_shaders[static_cast<u32>(program)];
|
||||
}
|
||||
|
||||
const VAddr program_addr{GetShaderAddress(program)};
|
||||
auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
|
||||
const Tegra::GPUVAddr program_addr{GetShaderAddress(program)};
|
||||
|
||||
// Look up shader in the cache based on address
|
||||
Shader shader{TryGet(program_addr)};
|
||||
const auto& host_ptr{memory_manager.GetPointer(program_addr)};
|
||||
Shader shader{TryGet(host_ptr)};
|
||||
|
||||
if (!shader) {
|
||||
// No shader found - create a new one
|
||||
ProgramCode program_code = GetShaderCode(program_addr);
|
||||
ProgramCode program_code{GetShaderCode(host_ptr)};
|
||||
ProgramCode program_code_b;
|
||||
if (program == Maxwell::ShaderProgram::VertexA) {
|
||||
program_code_b = GetShaderCode(GetShaderAddress(Maxwell::ShaderProgram::VertexB));
|
||||
program_code_b = GetShaderCode(
|
||||
memory_manager.GetPointer(GetShaderAddress(Maxwell::ShaderProgram::VertexB)));
|
||||
}
|
||||
const u64 unique_identifier = GetUniqueIdentifier(program, program_code, program_code_b);
|
||||
|
||||
const VAddr cpu_addr{*memory_manager.GpuToCpuAddress(program_addr)};
|
||||
const auto found = precompiled_shaders.find(unique_identifier);
|
||||
if (found != precompiled_shaders.end()) {
|
||||
shader =
|
||||
std::make_shared<CachedShader>(program_addr, unique_identifier, program, disk_cache,
|
||||
precompiled_programs, found->second);
|
||||
std::make_shared<CachedShader>(cpu_addr, unique_identifier, program, disk_cache,
|
||||
precompiled_programs, found->second, host_ptr);
|
||||
} else {
|
||||
shader = std::make_shared<CachedShader>(
|
||||
program_addr, unique_identifier, program, disk_cache, precompiled_programs,
|
||||
std::move(program_code), std::move(program_code_b));
|
||||
cpu_addr, unique_identifier, program, disk_cache, precompiled_programs,
|
||||
std::move(program_code), std::move(program_code_b), host_ptr);
|
||||
}
|
||||
Register(shader);
|
||||
}
|
||||
|
||||
@@ -39,18 +39,18 @@ using PrecompiledShaders = std::unordered_map<u64, GLShader::ProgramResult>;
|
||||
|
||||
class CachedShader final : public RasterizerCacheObject {
|
||||
public:
|
||||
explicit CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type,
|
||||
ShaderDiskCacheOpenGL& disk_cache,
|
||||
explicit CachedShader(VAddr cpu_addr, u64 unique_identifier,
|
||||
Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache,
|
||||
const PrecompiledPrograms& precompiled_programs,
|
||||
ProgramCode&& program_code, ProgramCode&& program_code_b);
|
||||
ProgramCode&& program_code, ProgramCode&& program_code_b, u8* host_ptr);
|
||||
|
||||
explicit CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type,
|
||||
ShaderDiskCacheOpenGL& disk_cache,
|
||||
explicit CachedShader(VAddr cpu_addr, u64 unique_identifier,
|
||||
Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache,
|
||||
const PrecompiledPrograms& precompiled_programs,
|
||||
GLShader::ProgramResult result);
|
||||
GLShader::ProgramResult result, u8* host_ptr);
|
||||
|
||||
VAddr GetAddr() const override {
|
||||
return addr;
|
||||
VAddr GetCpuAddr() const override {
|
||||
return cpu_addr;
|
||||
}
|
||||
|
||||
std::size_t GetSizeInBytes() const override {
|
||||
@@ -91,7 +91,8 @@ private:
|
||||
|
||||
ShaderDiskCacheUsage GetUsage(GLenum primitive_mode, BaseBindings base_bindings) const;
|
||||
|
||||
VAddr addr{};
|
||||
u8* host_ptr{};
|
||||
VAddr cpu_addr{};
|
||||
u64 unique_identifier{};
|
||||
Maxwell::ShaderProgram program_type{};
|
||||
ShaderDiskCacheOpenGL& disk_cache;
|
||||
|
||||
@@ -164,12 +164,13 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf
|
||||
// Reset the screen info's display texture to its own permanent texture
|
||||
screen_info.display_texture = screen_info.texture.resource.handle;
|
||||
|
||||
Memory::RasterizerFlushVirtualRegion(framebuffer_addr, size_in_bytes,
|
||||
Memory::FlushMode::Flush);
|
||||
rasterizer->FlushRegion(ToCacheAddr(Memory::GetPointer(framebuffer_addr)), size_in_bytes);
|
||||
|
||||
VideoCore::MortonCopyPixels128(framebuffer.width, framebuffer.height, bytes_per_pixel, 4,
|
||||
Memory::GetPointer(framebuffer_addr),
|
||||
gl_framebuffer_data.data(), true);
|
||||
constexpr u32 linear_bpp = 4;
|
||||
VideoCore::MortonCopyPixels128(VideoCore::MortonSwizzleMode::MortonToLinear,
|
||||
framebuffer.width, framebuffer.height, bytes_per_pixel,
|
||||
linear_bpp, Memory::GetPointer(framebuffer_addr),
|
||||
gl_framebuffer_data.data());
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(framebuffer.stride));
|
||||
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
CachedBufferEntry::CachedBufferEntry(VAddr cpu_addr, std::size_t size, u64 offset,
|
||||
std::size_t alignment, u8* host_ptr)
|
||||
: cpu_addr{cpu_addr}, size{size}, offset{offset}, alignment{alignment}, RasterizerCacheObject{
|
||||
host_ptr} {}
|
||||
|
||||
VKBufferCache::VKBufferCache(Tegra::MemoryManager& tegra_memory_manager,
|
||||
VideoCore::RasterizerInterface& rasterizer, const VKDevice& device,
|
||||
VKMemoryManager& memory_manager, VKScheduler& scheduler, u64 size)
|
||||
@@ -37,16 +42,18 @@ VKBufferCache::~VKBufferCache() = default;
|
||||
u64 VKBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, u64 alignment,
|
||||
bool cache) {
|
||||
const auto cpu_addr{tegra_memory_manager.GpuToCpuAddress(gpu_addr)};
|
||||
ASSERT(cpu_addr);
|
||||
ASSERT_MSG(cpu_addr, "Invalid GPU address");
|
||||
|
||||
// Cache management is a big overhead, so only cache entries with a given size.
|
||||
// TODO: Figure out which size is the best for given games.
|
||||
cache &= size >= 2048;
|
||||
|
||||
const auto& host_ptr{Memory::GetPointer(*cpu_addr)};
|
||||
if (cache) {
|
||||
if (auto entry = TryGet(*cpu_addr); entry) {
|
||||
if (entry->size >= size && entry->alignment == alignment) {
|
||||
return entry->offset;
|
||||
auto entry = TryGet(host_ptr);
|
||||
if (entry) {
|
||||
if (entry->GetSize() >= size && entry->GetAlignment() == alignment) {
|
||||
return entry->GetOffset();
|
||||
}
|
||||
Unregister(entry);
|
||||
}
|
||||
@@ -55,17 +62,17 @@ u64 VKBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, u64
|
||||
AlignBuffer(alignment);
|
||||
const u64 uploaded_offset = buffer_offset;
|
||||
|
||||
Memory::ReadBlock(*cpu_addr, buffer_ptr, size);
|
||||
if (!host_ptr) {
|
||||
return uploaded_offset;
|
||||
}
|
||||
|
||||
std::memcpy(buffer_ptr, host_ptr, size);
|
||||
buffer_ptr += size;
|
||||
buffer_offset += size;
|
||||
|
||||
if (cache) {
|
||||
auto entry = std::make_shared<CachedBufferEntry>();
|
||||
entry->offset = uploaded_offset;
|
||||
entry->size = size;
|
||||
entry->alignment = alignment;
|
||||
entry->addr = *cpu_addr;
|
||||
auto entry = std::make_shared<CachedBufferEntry>(*cpu_addr, size, uploaded_offset,
|
||||
alignment, host_ptr);
|
||||
Register(entry);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,22 +24,39 @@ class VKFence;
|
||||
class VKMemoryManager;
|
||||
class VKStreamBuffer;
|
||||
|
||||
struct CachedBufferEntry final : public RasterizerCacheObject {
|
||||
VAddr GetAddr() const override {
|
||||
return addr;
|
||||
class CachedBufferEntry final : public RasterizerCacheObject {
|
||||
public:
|
||||
explicit CachedBufferEntry(VAddr cpu_addr, std::size_t size, u64 offset, std::size_t alignment,
|
||||
u8* host_ptr);
|
||||
|
||||
VAddr GetCpuAddr() const override {
|
||||
return cpu_addr;
|
||||
}
|
||||
|
||||
std::size_t GetSizeInBytes() const override {
|
||||
return size;
|
||||
}
|
||||
|
||||
std::size_t GetSize() const {
|
||||
return size;
|
||||
}
|
||||
|
||||
u64 GetOffset() const {
|
||||
return offset;
|
||||
}
|
||||
|
||||
std::size_t GetAlignment() const {
|
||||
return alignment;
|
||||
}
|
||||
|
||||
// We do not have to flush this cache as things in it are never modified by us.
|
||||
void Flush() override {}
|
||||
|
||||
VAddr addr;
|
||||
std::size_t size;
|
||||
u64 offset;
|
||||
std::size_t alignment;
|
||||
private:
|
||||
VAddr cpu_addr{};
|
||||
std::size_t size{};
|
||||
u64 offset{};
|
||||
std::size_t alignment{};
|
||||
};
|
||||
|
||||
class VKBufferCache final : public RasterizerCache<std::shared_ptr<CachedBufferEntry>> {
|
||||
|
||||
81
src/video_core/renderer_vulkan/vk_sampler_cache.cpp
Normal file
81
src/video_core/renderer_vulkan/vk_sampler_cache.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/cityhash.h"
|
||||
#include "video_core/renderer_vulkan/declarations.h"
|
||||
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
|
||||
#include "video_core/renderer_vulkan/vk_sampler_cache.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
static std::optional<vk::BorderColor> TryConvertBorderColor(std::array<float, 4> color) {
|
||||
// TODO(Rodrigo): Manage integer border colors
|
||||
if (color == std::array<float, 4>{0, 0, 0, 0}) {
|
||||
return vk::BorderColor::eFloatTransparentBlack;
|
||||
} else if (color == std::array<float, 4>{0, 0, 0, 1}) {
|
||||
return vk::BorderColor::eFloatOpaqueBlack;
|
||||
} else if (color == std::array<float, 4>{1, 1, 1, 1}) {
|
||||
return vk::BorderColor::eFloatOpaqueWhite;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t SamplerCacheKey::Hash() const {
|
||||
static_assert(sizeof(raw) % sizeof(u64) == 0);
|
||||
return static_cast<std::size_t>(
|
||||
Common::CityHash64(reinterpret_cast<const char*>(raw.data()), sizeof(raw) / sizeof(u64)));
|
||||
}
|
||||
|
||||
bool SamplerCacheKey::operator==(const SamplerCacheKey& rhs) const {
|
||||
return raw == rhs.raw;
|
||||
}
|
||||
|
||||
VKSamplerCache::VKSamplerCache(const VKDevice& device) : device{device} {}
|
||||
|
||||
VKSamplerCache::~VKSamplerCache() = default;
|
||||
|
||||
vk::Sampler VKSamplerCache::GetSampler(const Tegra::Texture::TSCEntry& tsc) {
|
||||
const auto [entry, is_cache_miss] = cache.try_emplace(SamplerCacheKey{tsc});
|
||||
auto& sampler = entry->second;
|
||||
if (is_cache_miss) {
|
||||
sampler = CreateSampler(tsc);
|
||||
}
|
||||
return *sampler;
|
||||
}
|
||||
|
||||
UniqueSampler VKSamplerCache::CreateSampler(const Tegra::Texture::TSCEntry& tsc) {
|
||||
const float max_anisotropy = tsc.GetMaxAnisotropy();
|
||||
const bool has_anisotropy = max_anisotropy > 1.0f;
|
||||
|
||||
const auto border_color = tsc.GetBorderColor();
|
||||
const auto vk_border_color = TryConvertBorderColor(border_color);
|
||||
UNIMPLEMENTED_IF_MSG(!vk_border_color, "Unimplemented border color {} {} {} {}",
|
||||
border_color[0], border_color[1], border_color[2], border_color[3]);
|
||||
|
||||
constexpr bool unnormalized_coords = false;
|
||||
|
||||
const vk::SamplerCreateInfo sampler_ci(
|
||||
{}, MaxwellToVK::Sampler::Filter(tsc.mag_filter),
|
||||
MaxwellToVK::Sampler::Filter(tsc.min_filter),
|
||||
MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter),
|
||||
MaxwellToVK::Sampler::WrapMode(tsc.wrap_u), MaxwellToVK::Sampler::WrapMode(tsc.wrap_v),
|
||||
MaxwellToVK::Sampler::WrapMode(tsc.wrap_p), tsc.GetLodBias(), has_anisotropy,
|
||||
max_anisotropy, tsc.depth_compare_enabled,
|
||||
MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), tsc.GetMinLod(),
|
||||
tsc.GetMaxLod(), vk_border_color.value_or(vk::BorderColor::eFloatTransparentBlack),
|
||||
unnormalized_coords);
|
||||
|
||||
const auto& dld = device.GetDispatchLoader();
|
||||
const auto dev = device.GetLogical();
|
||||
return dev.createSamplerUnique(sampler_ci, nullptr, dld);
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
56
src/video_core/renderer_vulkan/vk_sampler_cache.h
Normal file
56
src/video_core/renderer_vulkan/vk_sampler_cache.h
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/declarations.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class VKDevice;
|
||||
|
||||
struct SamplerCacheKey final : public Tegra::Texture::TSCEntry {
|
||||
std::size_t Hash() const;
|
||||
|
||||
bool operator==(const SamplerCacheKey& rhs) const;
|
||||
|
||||
bool operator!=(const SamplerCacheKey& rhs) const {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<Vulkan::SamplerCacheKey> {
|
||||
std::size_t operator()(const Vulkan::SamplerCacheKey& k) const noexcept {
|
||||
return k.Hash();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class VKSamplerCache {
|
||||
public:
|
||||
explicit VKSamplerCache(const VKDevice& device);
|
||||
~VKSamplerCache();
|
||||
|
||||
vk::Sampler GetSampler(const Tegra::Texture::TSCEntry& tsc);
|
||||
|
||||
private:
|
||||
UniqueSampler CreateSampler(const Tegra::Texture::TSCEntry& tsc);
|
||||
|
||||
const VKDevice& device;
|
||||
std::unordered_map<SamplerCacheKey, UniqueSampler> cache;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <cstring>
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
@@ -230,18 +229,18 @@ u32 BytesPerPixel(TextureFormat format) {
|
||||
}
|
||||
}
|
||||
|
||||
void UnswizzleTexture(u8* const unswizzled_data, VAddr address, u32 tile_size_x, u32 tile_size_y,
|
||||
void UnswizzleTexture(u8* const unswizzled_data, u8* address, u32 tile_size_x, u32 tile_size_y,
|
||||
u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height,
|
||||
u32 block_depth, u32 width_spacing) {
|
||||
CopySwizzledData((width + tile_size_x - 1) / tile_size_x,
|
||||
(height + tile_size_y - 1) / tile_size_y, depth, bytes_per_pixel,
|
||||
bytes_per_pixel, Memory::GetPointer(address), unswizzled_data, true,
|
||||
block_height, block_depth, width_spacing);
|
||||
bytes_per_pixel, address, unswizzled_data, true, block_height, block_depth,
|
||||
width_spacing);
|
||||
}
|
||||
|
||||
std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y,
|
||||
u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
|
||||
u32 block_height, u32 block_depth, u32 width_spacing) {
|
||||
std::vector<u8> UnswizzleTexture(u8* address, u32 tile_size_x, u32 tile_size_y, u32 bytes_per_pixel,
|
||||
u32 width, u32 height, u32 depth, u32 block_height,
|
||||
u32 block_depth, u32 width_spacing) {
|
||||
std::vector<u8> unswizzled_data(width * height * depth * bytes_per_pixel);
|
||||
UnswizzleTexture(unswizzled_data.data(), address, tile_size_x, tile_size_y, bytes_per_pixel,
|
||||
width, height, depth, block_height, block_depth, width_spacing);
|
||||
@@ -249,8 +248,7 @@ std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y
|
||||
}
|
||||
|
||||
void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width,
|
||||
u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data,
|
||||
u32 block_height) {
|
||||
u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height) {
|
||||
const u32 image_width_in_gobs{(swizzled_width * bytes_per_pixel + (gob_size_x - 1)) /
|
||||
gob_size_x};
|
||||
for (u32 line = 0; line < subrect_height; ++line) {
|
||||
@@ -262,17 +260,17 @@ void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32
|
||||
const u32 gob_address =
|
||||
gob_address_y + (x * bytes_per_pixel / gob_size_x) * gob_size * block_height;
|
||||
const u32 swizzled_offset = gob_address + table[(x * bytes_per_pixel) % gob_size_x];
|
||||
const VAddr source_line = unswizzled_data + line * source_pitch + x * bytes_per_pixel;
|
||||
const VAddr dest_addr = swizzled_data + swizzled_offset;
|
||||
u8* source_line = unswizzled_data + line * source_pitch + x * bytes_per_pixel;
|
||||
u8* dest_addr = swizzled_data + swizzled_offset;
|
||||
|
||||
Memory::CopyBlock(dest_addr, source_line, bytes_per_pixel);
|
||||
std::memcpy(dest_addr, source_line, bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width,
|
||||
u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data,
|
||||
u32 block_height, u32 offset_x, u32 offset_y) {
|
||||
u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height,
|
||||
u32 offset_x, u32 offset_y) {
|
||||
for (u32 line = 0; line < subrect_height; ++line) {
|
||||
const u32 y2 = line + offset_y;
|
||||
const u32 gob_address_y = (y2 / (gob_size_y * block_height)) * gob_size * block_height +
|
||||
@@ -282,10 +280,10 @@ void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32
|
||||
const u32 x2 = (x + offset_x) * bytes_per_pixel;
|
||||
const u32 gob_address = gob_address_y + (x2 / gob_size_x) * gob_size * block_height;
|
||||
const u32 swizzled_offset = gob_address + table[x2 % gob_size_x];
|
||||
const VAddr dest_line = unswizzled_data + line * dest_pitch + x * bytes_per_pixel;
|
||||
const VAddr source_addr = swizzled_data + swizzled_offset;
|
||||
u8* dest_line = unswizzled_data + line * dest_pitch + x * bytes_per_pixel;
|
||||
u8* source_addr = swizzled_data + swizzled_offset;
|
||||
|
||||
Memory::CopyBlock(dest_line, source_addr, bytes_per_pixel);
|
||||
std::memcpy(dest_line, source_addr, bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,14 +17,14 @@ inline std::size_t GetGOBSize() {
|
||||
}
|
||||
|
||||
/// Unswizzles a swizzled texture without changing its format.
|
||||
void UnswizzleTexture(u8* unswizzled_data, VAddr address, u32 tile_size_x, u32 tile_size_y,
|
||||
void UnswizzleTexture(u8* unswizzled_data, u8* address, u32 tile_size_x, u32 tile_size_y,
|
||||
u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
|
||||
u32 block_height = TICEntry::DefaultBlockHeight,
|
||||
u32 block_depth = TICEntry::DefaultBlockHeight, u32 width_spacing = 0);
|
||||
|
||||
/// Unswizzles a swizzled texture without changing its format.
|
||||
std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y,
|
||||
u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
|
||||
std::vector<u8> UnswizzleTexture(u8* address, u32 tile_size_x, u32 tile_size_y, u32 bytes_per_pixel,
|
||||
u32 width, u32 height, u32 depth,
|
||||
u32 block_height = TICEntry::DefaultBlockHeight,
|
||||
u32 block_depth = TICEntry::DefaultBlockHeight,
|
||||
u32 width_spacing = 0);
|
||||
@@ -44,12 +44,11 @@ std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height
|
||||
|
||||
/// Copies an untiled subrectangle into a tiled surface.
|
||||
void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width,
|
||||
u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data,
|
||||
u32 block_height);
|
||||
u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height);
|
||||
|
||||
/// Copies a tiled subrectangle into a linear surface.
|
||||
void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width,
|
||||
u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data,
|
||||
u32 block_height, u32 offset_x, u32 offset_y);
|
||||
u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height,
|
||||
u32 offset_x, u32 offset_y);
|
||||
|
||||
} // namespace Tegra::Texture
|
||||
|
||||
@@ -283,31 +283,36 @@ enum class TextureMipmapFilter : u32 {
|
||||
|
||||
struct TSCEntry {
|
||||
union {
|
||||
BitField<0, 3, WrapMode> wrap_u;
|
||||
BitField<3, 3, WrapMode> wrap_v;
|
||||
BitField<6, 3, WrapMode> wrap_p;
|
||||
BitField<9, 1, u32> depth_compare_enabled;
|
||||
BitField<10, 3, DepthCompareFunc> depth_compare_func;
|
||||
BitField<13, 1, u32> srgb_conversion;
|
||||
BitField<20, 3, u32> max_anisotropy;
|
||||
struct {
|
||||
union {
|
||||
BitField<0, 3, WrapMode> wrap_u;
|
||||
BitField<3, 3, WrapMode> wrap_v;
|
||||
BitField<6, 3, WrapMode> wrap_p;
|
||||
BitField<9, 1, u32> depth_compare_enabled;
|
||||
BitField<10, 3, DepthCompareFunc> depth_compare_func;
|
||||
BitField<13, 1, u32> srgb_conversion;
|
||||
BitField<20, 3, u32> max_anisotropy;
|
||||
};
|
||||
union {
|
||||
BitField<0, 2, TextureFilter> mag_filter;
|
||||
BitField<4, 2, TextureFilter> min_filter;
|
||||
BitField<6, 2, TextureMipmapFilter> mipmap_filter;
|
||||
BitField<9, 1, u32> cubemap_interface_filtering;
|
||||
BitField<12, 13, u32> mip_lod_bias;
|
||||
};
|
||||
union {
|
||||
BitField<0, 12, u32> min_lod_clamp;
|
||||
BitField<12, 12, u32> max_lod_clamp;
|
||||
BitField<24, 8, u32> srgb_border_color_r;
|
||||
};
|
||||
union {
|
||||
BitField<12, 8, u32> srgb_border_color_g;
|
||||
BitField<20, 8, u32> srgb_border_color_b;
|
||||
};
|
||||
std::array<f32, 4> border_color;
|
||||
};
|
||||
std::array<u8, 0x20> raw;
|
||||
};
|
||||
union {
|
||||
BitField<0, 2, TextureFilter> mag_filter;
|
||||
BitField<4, 2, TextureFilter> min_filter;
|
||||
BitField<6, 2, TextureMipmapFilter> mipmap_filter;
|
||||
BitField<9, 1, u32> cubemap_interface_filtering;
|
||||
BitField<12, 13, u32> mip_lod_bias;
|
||||
};
|
||||
union {
|
||||
BitField<0, 12, u32> min_lod_clamp;
|
||||
BitField<12, 12, u32> max_lod_clamp;
|
||||
BitField<24, 8, u32> srgb_border_color_r;
|
||||
};
|
||||
union {
|
||||
BitField<12, 8, u32> srgb_border_color_g;
|
||||
BitField<20, 8, u32> srgb_border_color_b;
|
||||
};
|
||||
std::array<f32, 4> border_color;
|
||||
|
||||
float GetMaxAnisotropy() const {
|
||||
return static_cast<float>(1U << max_anisotropy);
|
||||
@@ -324,7 +329,7 @@ struct TSCEntry {
|
||||
float GetLodBias() const {
|
||||
// Sign extend the 13-bit value.
|
||||
constexpr u32 mask = 1U << (13 - 1);
|
||||
return static_cast<float>((mip_lod_bias ^ mask) - mask) / 256.0f;
|
||||
return static_cast<s32>((mip_lod_bias ^ mask) - mask) / 256.0f;
|
||||
}
|
||||
|
||||
std::array<float, 4> GetBorderColor() const {
|
||||
|
||||
@@ -24,8 +24,6 @@ void EmuThread::run() {
|
||||
|
||||
MicroProfileOnThreadCreate("EmuThread");
|
||||
|
||||
stop_run = false;
|
||||
|
||||
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
|
||||
|
||||
Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources(
|
||||
@@ -40,7 +38,7 @@ void EmuThread::run() {
|
||||
render_window->DoneCurrent();
|
||||
}
|
||||
|
||||
// holds whether the cpu was running during the last iteration,
|
||||
// Holds whether the cpu was running during the last iteration,
|
||||
// so that the DebugModeLeft signal can be emitted before the
|
||||
// next execution step
|
||||
bool was_active = false;
|
||||
@@ -123,7 +121,6 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
|
||||
setAttribute(Qt::WA_AcceptTouchEvents);
|
||||
|
||||
InputCommon::Init();
|
||||
InputCommon::StartJoystickEventHandler();
|
||||
connect(this, &GRenderWindow::FirstFrameDisplayed, static_cast<GMainWindow*>(parent),
|
||||
&GMainWindow::OnLoadComplete);
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ void Config::ReadPlayerValues() {
|
||||
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
|
||||
auto& player = Settings::values.players[p];
|
||||
|
||||
player.connected = qt_config->value(QString("player_%1_connected").arg(p), false).toBool();
|
||||
player.connected = ReadSetting(QString("player_%1_connected").arg(p), false).toBool();
|
||||
|
||||
player.type = static_cast<Settings::ControllerType>(
|
||||
qt_config
|
||||
@@ -269,7 +269,7 @@ void Config::ReadPlayerValues() {
|
||||
}
|
||||
|
||||
void Config::ReadDebugValues() {
|
||||
Settings::values.debug_pad_enabled = qt_config->value("debug_pad_enabled", false).toBool();
|
||||
Settings::values.debug_pad_enabled = ReadSetting("debug_pad_enabled", false).toBool();
|
||||
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
|
||||
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
|
||||
Settings::values.debug_pad_buttons[i] =
|
||||
@@ -298,7 +298,7 @@ void Config::ReadDebugValues() {
|
||||
}
|
||||
|
||||
void Config::ReadKeyboardValues() {
|
||||
Settings::values.keyboard_enabled = qt_config->value("keyboard_enabled", false).toBool();
|
||||
Settings::values.keyboard_enabled = ReadSetting("keyboard_enabled", false).toBool();
|
||||
|
||||
std::transform(default_keyboard_keys.begin(), default_keyboard_keys.end(),
|
||||
Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam);
|
||||
@@ -311,7 +311,7 @@ void Config::ReadKeyboardValues() {
|
||||
}
|
||||
|
||||
void Config::ReadMouseValues() {
|
||||
Settings::values.mouse_enabled = qt_config->value("mouse_enabled", false).toBool();
|
||||
Settings::values.mouse_enabled = ReadSetting("mouse_enabled", false).toBool();
|
||||
|
||||
for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) {
|
||||
std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]);
|
||||
@@ -327,16 +327,14 @@ void Config::ReadMouseValues() {
|
||||
}
|
||||
|
||||
void Config::ReadTouchscreenValues() {
|
||||
Settings::values.touchscreen.enabled = qt_config->value("touchscreen_enabled", true).toBool();
|
||||
Settings::values.touchscreen.enabled = ReadSetting("touchscreen_enabled", true).toBool();
|
||||
Settings::values.touchscreen.device =
|
||||
qt_config->value("touchscreen_device", "engine:emu_window").toString().toStdString();
|
||||
ReadSetting("touchscreen_device", "engine:emu_window").toString().toStdString();
|
||||
|
||||
Settings::values.touchscreen.finger = qt_config->value("touchscreen_finger", 0).toUInt();
|
||||
Settings::values.touchscreen.rotation_angle = qt_config->value("touchscreen_angle", 0).toUInt();
|
||||
Settings::values.touchscreen.diameter_x =
|
||||
qt_config->value("touchscreen_diameter_x", 15).toUInt();
|
||||
Settings::values.touchscreen.diameter_y =
|
||||
qt_config->value("touchscreen_diameter_y", 15).toUInt();
|
||||
Settings::values.touchscreen.finger = ReadSetting("touchscreen_finger", 0).toUInt();
|
||||
Settings::values.touchscreen.rotation_angle = ReadSetting("touchscreen_angle", 0).toUInt();
|
||||
Settings::values.touchscreen.diameter_x = ReadSetting("touchscreen_diameter_x", 15).toUInt();
|
||||
Settings::values.touchscreen.diameter_y = ReadSetting("touchscreen_diameter_y", 15).toUInt();
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
@@ -357,42 +355,41 @@ void Config::ReadValues() {
|
||||
ReadTouchscreenValues();
|
||||
|
||||
Settings::values.motion_device =
|
||||
qt_config->value("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01")
|
||||
ReadSetting("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01")
|
||||
.toString()
|
||||
.toStdString();
|
||||
|
||||
qt_config->beginGroup("Core");
|
||||
Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool();
|
||||
Settings::values.use_multi_core = qt_config->value("use_multi_core", false).toBool();
|
||||
Settings::values.use_cpu_jit = ReadSetting("use_cpu_jit", true).toBool();
|
||||
Settings::values.use_multi_core = ReadSetting("use_multi_core", false).toBool();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Renderer");
|
||||
Settings::values.resolution_factor = qt_config->value("resolution_factor", 1.0).toFloat();
|
||||
Settings::values.use_frame_limit = qt_config->value("use_frame_limit", true).toBool();
|
||||
Settings::values.frame_limit = qt_config->value("frame_limit", 100).toInt();
|
||||
Settings::values.use_disk_shader_cache =
|
||||
qt_config->value("use_disk_shader_cache", false).toBool();
|
||||
Settings::values.resolution_factor = ReadSetting("resolution_factor", 1.0).toFloat();
|
||||
Settings::values.use_frame_limit = ReadSetting("use_frame_limit", true).toBool();
|
||||
Settings::values.frame_limit = ReadSetting("frame_limit", 100).toInt();
|
||||
Settings::values.use_disk_shader_cache = ReadSetting("use_disk_shader_cache", true).toBool();
|
||||
Settings::values.use_accurate_gpu_emulation =
|
||||
qt_config->value("use_accurate_gpu_emulation", false).toBool();
|
||||
ReadSetting("use_accurate_gpu_emulation", false).toBool();
|
||||
Settings::values.use_asynchronous_gpu_emulation =
|
||||
qt_config->value("use_asynchronous_gpu_emulation", false).toBool();
|
||||
ReadSetting("use_asynchronous_gpu_emulation", false).toBool();
|
||||
|
||||
Settings::values.bg_red = qt_config->value("bg_red", 0.0).toFloat();
|
||||
Settings::values.bg_green = qt_config->value("bg_green", 0.0).toFloat();
|
||||
Settings::values.bg_blue = qt_config->value("bg_blue", 0.0).toFloat();
|
||||
Settings::values.bg_red = ReadSetting("bg_red", 0.0).toFloat();
|
||||
Settings::values.bg_green = ReadSetting("bg_green", 0.0).toFloat();
|
||||
Settings::values.bg_blue = ReadSetting("bg_blue", 0.0).toFloat();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Audio");
|
||||
Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString();
|
||||
Settings::values.sink_id = ReadSetting("output_engine", "auto").toString().toStdString();
|
||||
Settings::values.enable_audio_stretching =
|
||||
qt_config->value("enable_audio_stretching", true).toBool();
|
||||
ReadSetting("enable_audio_stretching", true).toBool();
|
||||
Settings::values.audio_device_id =
|
||||
qt_config->value("output_device", "auto").toString().toStdString();
|
||||
Settings::values.volume = qt_config->value("volume", 1).toFloat();
|
||||
ReadSetting("output_device", "auto").toString().toStdString();
|
||||
Settings::values.volume = ReadSetting("volume", 1).toFloat();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Data Storage");
|
||||
Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool();
|
||||
Settings::values.use_virtual_sd = ReadSetting("use_virtual_sd", true).toBool();
|
||||
FileUtil::GetUserPath(
|
||||
FileUtil::UserPath::NANDDir,
|
||||
qt_config
|
||||
@@ -410,30 +407,30 @@ void Config::ReadValues() {
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Core");
|
||||
Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool();
|
||||
Settings::values.use_multi_core = qt_config->value("use_multi_core", false).toBool();
|
||||
Settings::values.use_cpu_jit = ReadSetting("use_cpu_jit", true).toBool();
|
||||
Settings::values.use_multi_core = ReadSetting("use_multi_core", false).toBool();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("System");
|
||||
Settings::values.use_docked_mode = qt_config->value("use_docked_mode", false).toBool();
|
||||
Settings::values.enable_nfc = qt_config->value("enable_nfc", true).toBool();
|
||||
Settings::values.use_docked_mode = ReadSetting("use_docked_mode", false).toBool();
|
||||
Settings::values.enable_nfc = ReadSetting("enable_nfc", true).toBool();
|
||||
|
||||
Settings::values.current_user = std::clamp<int>(qt_config->value("current_user", 0).toInt(), 0,
|
||||
Service::Account::MAX_USERS - 1);
|
||||
Settings::values.current_user =
|
||||
std::clamp<int>(ReadSetting("current_user", 0).toInt(), 0, Service::Account::MAX_USERS - 1);
|
||||
|
||||
Settings::values.language_index = qt_config->value("language_index", 1).toInt();
|
||||
Settings::values.language_index = ReadSetting("language_index", 1).toInt();
|
||||
|
||||
const auto rng_seed_enabled = qt_config->value("rng_seed_enabled", false).toBool();
|
||||
const auto rng_seed_enabled = ReadSetting("rng_seed_enabled", false).toBool();
|
||||
if (rng_seed_enabled) {
|
||||
Settings::values.rng_seed = qt_config->value("rng_seed", 0).toULongLong();
|
||||
Settings::values.rng_seed = ReadSetting("rng_seed", 0).toULongLong();
|
||||
} else {
|
||||
Settings::values.rng_seed = std::nullopt;
|
||||
}
|
||||
|
||||
const auto custom_rtc_enabled = qt_config->value("custom_rtc_enabled", false).toBool();
|
||||
const auto custom_rtc_enabled = ReadSetting("custom_rtc_enabled", false).toBool();
|
||||
if (custom_rtc_enabled) {
|
||||
Settings::values.custom_rtc =
|
||||
std::chrono::seconds(qt_config->value("custom_rtc", 0).toULongLong());
|
||||
std::chrono::seconds(ReadSetting("custom_rtc", 0).toULongLong());
|
||||
} else {
|
||||
Settings::values.custom_rtc = std::nullopt;
|
||||
}
|
||||
@@ -441,35 +438,35 @@ void Config::ReadValues() {
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Miscellaneous");
|
||||
Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString();
|
||||
Settings::values.use_dev_keys = qt_config->value("use_dev_keys", false).toBool();
|
||||
Settings::values.log_filter = ReadSetting("log_filter", "*:Info").toString().toStdString();
|
||||
Settings::values.use_dev_keys = ReadSetting("use_dev_keys", false).toBool();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Debugging");
|
||||
Settings::values.use_gdbstub = qt_config->value("use_gdbstub", false).toBool();
|
||||
Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt();
|
||||
Settings::values.program_args = qt_config->value("program_args", "").toString().toStdString();
|
||||
Settings::values.dump_exefs = qt_config->value("dump_exefs", false).toBool();
|
||||
Settings::values.dump_nso = qt_config->value("dump_nso", false).toBool();
|
||||
Settings::values.use_gdbstub = ReadSetting("use_gdbstub", false).toBool();
|
||||
Settings::values.gdbstub_port = ReadSetting("gdbstub_port", 24689).toInt();
|
||||
Settings::values.program_args = ReadSetting("program_args", "").toString().toStdString();
|
||||
Settings::values.dump_exefs = ReadSetting("dump_exefs", false).toBool();
|
||||
Settings::values.dump_nso = ReadSetting("dump_nso", false).toBool();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("WebService");
|
||||
Settings::values.enable_telemetry = qt_config->value("enable_telemetry", true).toBool();
|
||||
Settings::values.enable_telemetry = ReadSetting("enable_telemetry", true).toBool();
|
||||
Settings::values.web_api_url =
|
||||
qt_config->value("web_api_url", "https://api.yuzu-emu.org").toString().toStdString();
|
||||
Settings::values.yuzu_username = qt_config->value("yuzu_username").toString().toStdString();
|
||||
Settings::values.yuzu_token = qt_config->value("yuzu_token").toString().toStdString();
|
||||
ReadSetting("web_api_url", "https://api.yuzu-emu.org").toString().toStdString();
|
||||
Settings::values.yuzu_username = ReadSetting("yuzu_username").toString().toStdString();
|
||||
Settings::values.yuzu_token = ReadSetting("yuzu_token").toString().toStdString();
|
||||
qt_config->endGroup();
|
||||
|
||||
const auto size = qt_config->beginReadArray("DisabledAddOns");
|
||||
for (int i = 0; i < size; ++i) {
|
||||
qt_config->setArrayIndex(i);
|
||||
const auto title_id = qt_config->value("title_id", 0).toULongLong();
|
||||
const auto title_id = ReadSetting("title_id", 0).toULongLong();
|
||||
std::vector<std::string> out;
|
||||
const auto d_size = qt_config->beginReadArray("disabled");
|
||||
for (int j = 0; j < d_size; ++j) {
|
||||
qt_config->setArrayIndex(j);
|
||||
out.push_back(qt_config->value("d", "").toString().toStdString());
|
||||
out.push_back(ReadSetting("d", "").toString().toStdString());
|
||||
}
|
||||
qt_config->endArray();
|
||||
Settings::values.disabled_addons.insert_or_assign(title_id, out);
|
||||
@@ -477,41 +474,38 @@ void Config::ReadValues() {
|
||||
qt_config->endArray();
|
||||
|
||||
qt_config->beginGroup("UI");
|
||||
UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();
|
||||
UISettings::values.theme = ReadSetting("theme", UISettings::themes[0].second).toString();
|
||||
UISettings::values.enable_discord_presence =
|
||||
qt_config->value("enable_discord_presence", true).toBool();
|
||||
ReadSetting("enable_discord_presence", true).toBool();
|
||||
UISettings::values.screenshot_resolution_factor =
|
||||
static_cast<u16>(qt_config->value("screenshot_resolution_factor", 0).toUInt());
|
||||
UISettings::values.select_user_on_boot =
|
||||
qt_config->value("select_user_on_boot", false).toBool();
|
||||
static_cast<u16>(ReadSetting("screenshot_resolution_factor", 0).toUInt());
|
||||
UISettings::values.select_user_on_boot = ReadSetting("select_user_on_boot", false).toBool();
|
||||
|
||||
qt_config->beginGroup("UIGameList");
|
||||
UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool();
|
||||
UISettings::values.show_add_ons = qt_config->value("show_add_ons", true).toBool();
|
||||
UISettings::values.icon_size = qt_config->value("icon_size", 64).toUInt();
|
||||
UISettings::values.row_1_text_id = qt_config->value("row_1_text_id", 3).toUInt();
|
||||
UISettings::values.row_2_text_id = qt_config->value("row_2_text_id", 2).toUInt();
|
||||
UISettings::values.show_unknown = ReadSetting("show_unknown", true).toBool();
|
||||
UISettings::values.show_add_ons = ReadSetting("show_add_ons", true).toBool();
|
||||
UISettings::values.icon_size = ReadSetting("icon_size", 64).toUInt();
|
||||
UISettings::values.row_1_text_id = ReadSetting("row_1_text_id", 3).toUInt();
|
||||
UISettings::values.row_2_text_id = ReadSetting("row_2_text_id", 2).toUInt();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("UILayout");
|
||||
UISettings::values.geometry = qt_config->value("geometry").toByteArray();
|
||||
UISettings::values.state = qt_config->value("state").toByteArray();
|
||||
UISettings::values.renderwindow_geometry =
|
||||
qt_config->value("geometryRenderWindow").toByteArray();
|
||||
UISettings::values.gamelist_header_state =
|
||||
qt_config->value("gameListHeaderState").toByteArray();
|
||||
UISettings::values.geometry = ReadSetting("geometry").toByteArray();
|
||||
UISettings::values.state = ReadSetting("state").toByteArray();
|
||||
UISettings::values.renderwindow_geometry = ReadSetting("geometryRenderWindow").toByteArray();
|
||||
UISettings::values.gamelist_header_state = ReadSetting("gameListHeaderState").toByteArray();
|
||||
UISettings::values.microprofile_geometry =
|
||||
qt_config->value("microProfileDialogGeometry").toByteArray();
|
||||
ReadSetting("microProfileDialogGeometry").toByteArray();
|
||||
UISettings::values.microprofile_visible =
|
||||
qt_config->value("microProfileDialogVisible", false).toBool();
|
||||
ReadSetting("microProfileDialogVisible", false).toBool();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Paths");
|
||||
UISettings::values.roms_path = qt_config->value("romsPath").toString();
|
||||
UISettings::values.symbols_path = qt_config->value("symbolsPath").toString();
|
||||
UISettings::values.gamedir = qt_config->value("gameListRootDir", ".").toString();
|
||||
UISettings::values.gamedir_deepscan = qt_config->value("gameListDeepScan", false).toBool();
|
||||
UISettings::values.recent_files = qt_config->value("recentFiles").toStringList();
|
||||
UISettings::values.roms_path = ReadSetting("romsPath").toString();
|
||||
UISettings::values.symbols_path = ReadSetting("symbolsPath").toString();
|
||||
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");
|
||||
@@ -524,8 +518,8 @@ void Config::ReadValues() {
|
||||
qt_config->beginGroup(hotkey);
|
||||
UISettings::values.shortcuts.emplace_back(UISettings::Shortcut(
|
||||
group + "/" + hotkey,
|
||||
UISettings::ContextualShortcut(qt_config->value("KeySeq").toString(),
|
||||
qt_config->value("Context").toInt())));
|
||||
UISettings::ContextualShortcut(ReadSetting("KeySeq").toString(),
|
||||
ReadSetting("Context").toInt())));
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
@@ -533,16 +527,16 @@ void Config::ReadValues() {
|
||||
}
|
||||
qt_config->endGroup();
|
||||
|
||||
UISettings::values.single_window_mode = qt_config->value("singleWindowMode", true).toBool();
|
||||
UISettings::values.fullscreen = qt_config->value("fullscreen", false).toBool();
|
||||
UISettings::values.display_titlebar = qt_config->value("displayTitleBars", true).toBool();
|
||||
UISettings::values.show_filter_bar = qt_config->value("showFilterBar", true).toBool();
|
||||
UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool();
|
||||
UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool();
|
||||
UISettings::values.first_start = qt_config->value("firstStart", true).toBool();
|
||||
UISettings::values.callout_flags = qt_config->value("calloutFlags", 0).toUInt();
|
||||
UISettings::values.show_console = qt_config->value("showConsole", false).toBool();
|
||||
UISettings::values.profile_index = qt_config->value("profileIndex", 0).toUInt();
|
||||
UISettings::values.single_window_mode = ReadSetting("singleWindowMode", true).toBool();
|
||||
UISettings::values.fullscreen = ReadSetting("fullscreen", false).toBool();
|
||||
UISettings::values.display_titlebar = ReadSetting("displayTitleBars", true).toBool();
|
||||
UISettings::values.show_filter_bar = ReadSetting("showFilterBar", true).toBool();
|
||||
UISettings::values.show_status_bar = ReadSetting("showStatusBar", true).toBool();
|
||||
UISettings::values.confirm_before_closing = ReadSetting("confirmClose", true).toBool();
|
||||
UISettings::values.first_start = ReadSetting("firstStart", true).toBool();
|
||||
UISettings::values.callout_flags = ReadSetting("calloutFlags", 0).toUInt();
|
||||
UISettings::values.show_console = ReadSetting("showConsole", false).toBool();
|
||||
UISettings::values.profile_index = ReadSetting("profileIndex", 0).toUInt();
|
||||
|
||||
ApplyDefaultProfileIfInputInvalid();
|
||||
|
||||
@@ -553,62 +547,79 @@ void Config::SavePlayerValues() {
|
||||
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
|
||||
const auto& player = Settings::values.players[p];
|
||||
|
||||
qt_config->setValue(QString("player_%1_connected").arg(p), player.connected);
|
||||
qt_config->setValue(QString("player_%1_type").arg(p), static_cast<u8>(player.type));
|
||||
WriteSetting(QString("player_%1_connected").arg(p), player.connected, false);
|
||||
WriteSetting(QString("player_%1_type").arg(p), static_cast<u8>(player.type),
|
||||
static_cast<u8>(Settings::ControllerType::DualJoycon));
|
||||
|
||||
qt_config->setValue(QString("player_%1_body_color_left").arg(p), player.body_color_left);
|
||||
qt_config->setValue(QString("player_%1_body_color_right").arg(p), player.body_color_right);
|
||||
qt_config->setValue(QString("player_%1_button_color_left").arg(p),
|
||||
player.button_color_left);
|
||||
qt_config->setValue(QString("player_%1_button_color_right").arg(p),
|
||||
player.button_color_right);
|
||||
WriteSetting(QString("player_%1_body_color_left").arg(p), player.body_color_left,
|
||||
Settings::JOYCON_BODY_NEON_BLUE);
|
||||
WriteSetting(QString("player_%1_body_color_right").arg(p), player.body_color_right,
|
||||
Settings::JOYCON_BODY_NEON_RED);
|
||||
WriteSetting(QString("player_%1_button_color_left").arg(p), player.button_color_left,
|
||||
Settings::JOYCON_BUTTONS_NEON_BLUE);
|
||||
WriteSetting(QString("player_%1_button_color_right").arg(p), player.button_color_right,
|
||||
Settings::JOYCON_BUTTONS_NEON_RED);
|
||||
|
||||
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
|
||||
qt_config->setValue(QString("player_%1_").arg(p) +
|
||||
QString::fromStdString(Settings::NativeButton::mapping[i]),
|
||||
QString::fromStdString(player.buttons[i]));
|
||||
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
|
||||
WriteSetting(QString("player_%1_").arg(p) +
|
||||
QString::fromStdString(Settings::NativeButton::mapping[i]),
|
||||
QString::fromStdString(player.buttons[i]),
|
||||
QString::fromStdString(default_param));
|
||||
}
|
||||
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
|
||||
qt_config->setValue(QString("player_%1_").arg(p) +
|
||||
QString::fromStdString(Settings::NativeAnalog::mapping[i]),
|
||||
QString::fromStdString(player.analogs[i]));
|
||||
std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
|
||||
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
|
||||
default_analogs[i][3], default_analogs[i][4], 0.5f);
|
||||
WriteSetting(QString("player_%1_").arg(p) +
|
||||
QString::fromStdString(Settings::NativeAnalog::mapping[i]),
|
||||
QString::fromStdString(player.analogs[i]),
|
||||
QString::fromStdString(default_param));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Config::SaveDebugValues() {
|
||||
qt_config->setValue("debug_pad_enabled", Settings::values.debug_pad_enabled);
|
||||
WriteSetting("debug_pad_enabled", Settings::values.debug_pad_enabled, false);
|
||||
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
|
||||
qt_config->setValue(QString("debug_pad_") +
|
||||
QString::fromStdString(Settings::NativeButton::mapping[i]),
|
||||
QString::fromStdString(Settings::values.debug_pad_buttons[i]));
|
||||
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
|
||||
WriteSetting(QString("debug_pad_") +
|
||||
QString::fromStdString(Settings::NativeButton::mapping[i]),
|
||||
QString::fromStdString(Settings::values.debug_pad_buttons[i]),
|
||||
QString::fromStdString(default_param));
|
||||
}
|
||||
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
|
||||
qt_config->setValue(QString("debug_pad_") +
|
||||
QString::fromStdString(Settings::NativeAnalog::mapping[i]),
|
||||
QString::fromStdString(Settings::values.debug_pad_analogs[i]));
|
||||
std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
|
||||
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
|
||||
default_analogs[i][3], default_analogs[i][4], 0.5f);
|
||||
WriteSetting(QString("debug_pad_") +
|
||||
QString::fromStdString(Settings::NativeAnalog::mapping[i]),
|
||||
QString::fromStdString(Settings::values.debug_pad_analogs[i]),
|
||||
QString::fromStdString(default_param));
|
||||
}
|
||||
}
|
||||
|
||||
void Config::SaveMouseValues() {
|
||||
qt_config->setValue("mouse_enabled", Settings::values.mouse_enabled);
|
||||
WriteSetting("mouse_enabled", Settings::values.mouse_enabled, false);
|
||||
|
||||
for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) {
|
||||
qt_config->setValue(QString("mouse_") +
|
||||
QString::fromStdString(Settings::NativeMouseButton::mapping[i]),
|
||||
QString::fromStdString(Settings::values.mouse_buttons[i]));
|
||||
std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]);
|
||||
WriteSetting(QString("mouse_") +
|
||||
QString::fromStdString(Settings::NativeMouseButton::mapping[i]),
|
||||
QString::fromStdString(Settings::values.mouse_buttons[i]),
|
||||
QString::fromStdString(default_param));
|
||||
}
|
||||
}
|
||||
|
||||
void Config::SaveTouchscreenValues() {
|
||||
qt_config->setValue("touchscreen_enabled", Settings::values.touchscreen.enabled);
|
||||
qt_config->setValue("touchscreen_device",
|
||||
QString::fromStdString(Settings::values.touchscreen.device));
|
||||
WriteSetting("touchscreen_enabled", Settings::values.touchscreen.enabled, true);
|
||||
WriteSetting("touchscreen_device", QString::fromStdString(Settings::values.touchscreen.device),
|
||||
"engine:emu_window");
|
||||
|
||||
qt_config->setValue("touchscreen_finger", Settings::values.touchscreen.finger);
|
||||
qt_config->setValue("touchscreen_angle", Settings::values.touchscreen.rotation_angle);
|
||||
qt_config->setValue("touchscreen_diameter_x", Settings::values.touchscreen.diameter_x);
|
||||
qt_config->setValue("touchscreen_diameter_y", Settings::values.touchscreen.diameter_y);
|
||||
WriteSetting("touchscreen_finger", Settings::values.touchscreen.finger, 0);
|
||||
WriteSetting("touchscreen_angle", Settings::values.touchscreen.rotation_angle, 0);
|
||||
WriteSetting("touchscreen_diameter_x", Settings::values.touchscreen.diameter_x, 15);
|
||||
WriteSetting("touchscreen_diameter_y", Settings::values.touchscreen.diameter_y, 15);
|
||||
}
|
||||
|
||||
void Config::SaveValues() {
|
||||
@@ -619,91 +630,96 @@ void Config::SaveValues() {
|
||||
SaveMouseValues();
|
||||
SaveTouchscreenValues();
|
||||
|
||||
qt_config->setValue("motion_device", QString::fromStdString(Settings::values.motion_device));
|
||||
qt_config->setValue("keyboard_enabled", Settings::values.keyboard_enabled);
|
||||
WriteSetting("motion_device", QString::fromStdString(Settings::values.motion_device),
|
||||
"engine:motion_emu,update_period:100,sensitivity:0.01");
|
||||
WriteSetting("keyboard_enabled", Settings::values.keyboard_enabled, false);
|
||||
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Core");
|
||||
qt_config->setValue("use_cpu_jit", Settings::values.use_cpu_jit);
|
||||
qt_config->setValue("use_multi_core", Settings::values.use_multi_core);
|
||||
WriteSetting("use_cpu_jit", Settings::values.use_cpu_jit, true);
|
||||
WriteSetting("use_multi_core", Settings::values.use_multi_core, false);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Renderer");
|
||||
qt_config->setValue("resolution_factor", (double)Settings::values.resolution_factor);
|
||||
qt_config->setValue("use_frame_limit", Settings::values.use_frame_limit);
|
||||
qt_config->setValue("frame_limit", Settings::values.frame_limit);
|
||||
qt_config->setValue("use_disk_shader_cache", Settings::values.use_disk_shader_cache);
|
||||
qt_config->setValue("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation);
|
||||
qt_config->setValue("use_asynchronous_gpu_emulation",
|
||||
Settings::values.use_asynchronous_gpu_emulation);
|
||||
WriteSetting("resolution_factor", (double)Settings::values.resolution_factor, 1.0);
|
||||
WriteSetting("use_frame_limit", Settings::values.use_frame_limit, true);
|
||||
WriteSetting("frame_limit", Settings::values.frame_limit, 100);
|
||||
WriteSetting("use_disk_shader_cache", Settings::values.use_disk_shader_cache, true);
|
||||
WriteSetting("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation, false);
|
||||
WriteSetting("use_asynchronous_gpu_emulation", Settings::values.use_asynchronous_gpu_emulation,
|
||||
false);
|
||||
|
||||
// Cast to double because Qt's written float values are not human-readable
|
||||
qt_config->setValue("bg_red", (double)Settings::values.bg_red);
|
||||
qt_config->setValue("bg_green", (double)Settings::values.bg_green);
|
||||
qt_config->setValue("bg_blue", (double)Settings::values.bg_blue);
|
||||
WriteSetting("bg_red", (double)Settings::values.bg_red, 0.0);
|
||||
WriteSetting("bg_green", (double)Settings::values.bg_green, 0.0);
|
||||
WriteSetting("bg_blue", (double)Settings::values.bg_blue, 0.0);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Audio");
|
||||
qt_config->setValue("output_engine", QString::fromStdString(Settings::values.sink_id));
|
||||
qt_config->setValue("enable_audio_stretching", Settings::values.enable_audio_stretching);
|
||||
qt_config->setValue("output_device", QString::fromStdString(Settings::values.audio_device_id));
|
||||
qt_config->setValue("volume", Settings::values.volume);
|
||||
WriteSetting("output_engine", QString::fromStdString(Settings::values.sink_id), "auto");
|
||||
WriteSetting("enable_audio_stretching", Settings::values.enable_audio_stretching, true);
|
||||
WriteSetting("output_device", QString::fromStdString(Settings::values.audio_device_id), "auto");
|
||||
WriteSetting("volume", Settings::values.volume, 1.0f);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Data Storage");
|
||||
qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd);
|
||||
qt_config->setValue("nand_directory",
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
|
||||
qt_config->setValue("sdmc_directory",
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
|
||||
WriteSetting("use_virtual_sd", Settings::values.use_virtual_sd, true);
|
||||
WriteSetting("nand_directory",
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)),
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
|
||||
WriteSetting("sdmc_directory",
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)),
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("System");
|
||||
qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode);
|
||||
qt_config->setValue("enable_nfc", Settings::values.enable_nfc);
|
||||
qt_config->setValue("current_user", Settings::values.current_user);
|
||||
qt_config->setValue("language_index", Settings::values.language_index);
|
||||
WriteSetting("use_docked_mode", Settings::values.use_docked_mode, false);
|
||||
WriteSetting("enable_nfc", Settings::values.enable_nfc, true);
|
||||
WriteSetting("current_user", Settings::values.current_user, 0);
|
||||
WriteSetting("language_index", Settings::values.language_index, 1);
|
||||
|
||||
qt_config->setValue("rng_seed_enabled", Settings::values.rng_seed.has_value());
|
||||
qt_config->setValue("rng_seed", Settings::values.rng_seed.value_or(0));
|
||||
WriteSetting("rng_seed_enabled", Settings::values.rng_seed.has_value(), false);
|
||||
WriteSetting("rng_seed", Settings::values.rng_seed.value_or(0), 0);
|
||||
|
||||
qt_config->setValue("custom_rtc_enabled", Settings::values.custom_rtc.has_value());
|
||||
qt_config->setValue("custom_rtc",
|
||||
QVariant::fromValue<long long>(
|
||||
Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()));
|
||||
WriteSetting("custom_rtc_enabled", Settings::values.custom_rtc.has_value(), false);
|
||||
WriteSetting("custom_rtc",
|
||||
QVariant::fromValue<long long>(
|
||||
Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()),
|
||||
0);
|
||||
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Miscellaneous");
|
||||
qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter));
|
||||
qt_config->setValue("use_dev_keys", Settings::values.use_dev_keys);
|
||||
WriteSetting("log_filter", QString::fromStdString(Settings::values.log_filter), "*:Info");
|
||||
WriteSetting("use_dev_keys", Settings::values.use_dev_keys, false);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Debugging");
|
||||
qt_config->setValue("use_gdbstub", Settings::values.use_gdbstub);
|
||||
qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port);
|
||||
qt_config->setValue("program_args", QString::fromStdString(Settings::values.program_args));
|
||||
qt_config->setValue("dump_exefs", Settings::values.dump_exefs);
|
||||
qt_config->setValue("dump_nso", Settings::values.dump_nso);
|
||||
WriteSetting("use_gdbstub", Settings::values.use_gdbstub, false);
|
||||
WriteSetting("gdbstub_port", Settings::values.gdbstub_port, 24689);
|
||||
WriteSetting("program_args", QString::fromStdString(Settings::values.program_args), "");
|
||||
WriteSetting("dump_exefs", Settings::values.dump_exefs, false);
|
||||
WriteSetting("dump_nso", Settings::values.dump_nso, false);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("WebService");
|
||||
qt_config->setValue("enable_telemetry", Settings::values.enable_telemetry);
|
||||
qt_config->setValue("web_api_url", QString::fromStdString(Settings::values.web_api_url));
|
||||
qt_config->setValue("yuzu_username", QString::fromStdString(Settings::values.yuzu_username));
|
||||
qt_config->setValue("yuzu_token", QString::fromStdString(Settings::values.yuzu_token));
|
||||
WriteSetting("enable_telemetry", Settings::values.enable_telemetry, true);
|
||||
WriteSetting("web_api_url", QString::fromStdString(Settings::values.web_api_url),
|
||||
"https://api.yuzu-emu.org");
|
||||
WriteSetting("yuzu_username", QString::fromStdString(Settings::values.yuzu_username));
|
||||
WriteSetting("yuzu_token", QString::fromStdString(Settings::values.yuzu_token));
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginWriteArray("DisabledAddOns");
|
||||
int i = 0;
|
||||
for (const auto& elem : Settings::values.disabled_addons) {
|
||||
qt_config->setArrayIndex(i);
|
||||
qt_config->setValue("title_id", QVariant::fromValue<u64>(elem.first));
|
||||
WriteSetting("title_id", QVariant::fromValue<u64>(elem.first), 0);
|
||||
qt_config->beginWriteArray("disabled");
|
||||
for (std::size_t j = 0; j < elem.second.size(); ++j) {
|
||||
qt_config->setArrayIndex(static_cast<int>(j));
|
||||
qt_config->setValue("d", QString::fromStdString(elem.second[j]));
|
||||
WriteSetting("d", QString::fromStdString(elem.second[j]), "");
|
||||
}
|
||||
qt_config->endArray();
|
||||
++i;
|
||||
@@ -711,60 +727,86 @@ void Config::SaveValues() {
|
||||
qt_config->endArray();
|
||||
|
||||
qt_config->beginGroup("UI");
|
||||
qt_config->setValue("theme", UISettings::values.theme);
|
||||
qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence);
|
||||
qt_config->setValue("screenshot_resolution_factor",
|
||||
UISettings::values.screenshot_resolution_factor);
|
||||
qt_config->setValue("select_user_on_boot", UISettings::values.select_user_on_boot);
|
||||
WriteSetting("theme", UISettings::values.theme, UISettings::themes[0].second);
|
||||
WriteSetting("enable_discord_presence", UISettings::values.enable_discord_presence, true);
|
||||
WriteSetting("screenshot_resolution_factor", UISettings::values.screenshot_resolution_factor,
|
||||
0);
|
||||
WriteSetting("select_user_on_boot", UISettings::values.select_user_on_boot, false);
|
||||
|
||||
qt_config->beginGroup("UIGameList");
|
||||
qt_config->setValue("show_unknown", UISettings::values.show_unknown);
|
||||
qt_config->setValue("show_add_ons", UISettings::values.show_add_ons);
|
||||
qt_config->setValue("icon_size", UISettings::values.icon_size);
|
||||
qt_config->setValue("row_1_text_id", UISettings::values.row_1_text_id);
|
||||
qt_config->setValue("row_2_text_id", UISettings::values.row_2_text_id);
|
||||
WriteSetting("show_unknown", UISettings::values.show_unknown, true);
|
||||
WriteSetting("show_add_ons", UISettings::values.show_add_ons, true);
|
||||
WriteSetting("icon_size", UISettings::values.icon_size, 64);
|
||||
WriteSetting("row_1_text_id", UISettings::values.row_1_text_id, 3);
|
||||
WriteSetting("row_2_text_id", UISettings::values.row_2_text_id, 2);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("UILayout");
|
||||
qt_config->setValue("geometry", UISettings::values.geometry);
|
||||
qt_config->setValue("state", UISettings::values.state);
|
||||
qt_config->setValue("geometryRenderWindow", UISettings::values.renderwindow_geometry);
|
||||
qt_config->setValue("gameListHeaderState", UISettings::values.gamelist_header_state);
|
||||
qt_config->setValue("microProfileDialogGeometry", UISettings::values.microprofile_geometry);
|
||||
qt_config->setValue("microProfileDialogVisible", UISettings::values.microprofile_visible);
|
||||
WriteSetting("geometry", UISettings::values.geometry);
|
||||
WriteSetting("state", UISettings::values.state);
|
||||
WriteSetting("geometryRenderWindow", UISettings::values.renderwindow_geometry);
|
||||
WriteSetting("gameListHeaderState", UISettings::values.gamelist_header_state);
|
||||
WriteSetting("microProfileDialogGeometry", UISettings::values.microprofile_geometry);
|
||||
WriteSetting("microProfileDialogVisible", UISettings::values.microprofile_visible, false);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Paths");
|
||||
qt_config->setValue("romsPath", UISettings::values.roms_path);
|
||||
qt_config->setValue("symbolsPath", UISettings::values.symbols_path);
|
||||
qt_config->setValue("screenshotPath", UISettings::values.screenshot_path);
|
||||
qt_config->setValue("gameListRootDir", UISettings::values.gamedir);
|
||||
qt_config->setValue("gameListDeepScan", UISettings::values.gamedir_deepscan);
|
||||
qt_config->setValue("recentFiles", UISettings::values.recent_files);
|
||||
WriteSetting("romsPath", UISettings::values.roms_path);
|
||||
WriteSetting("symbolsPath", UISettings::values.symbols_path);
|
||||
WriteSetting("screenshotPath", UISettings::values.screenshot_path);
|
||||
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");
|
||||
for (auto shortcut : UISettings::values.shortcuts) {
|
||||
qt_config->setValue(shortcut.first + "/KeySeq", shortcut.second.first);
|
||||
qt_config->setValue(shortcut.first + "/Context", shortcut.second.second);
|
||||
WriteSetting(shortcut.first + "/KeySeq", shortcut.second.first);
|
||||
WriteSetting(shortcut.first + "/Context", shortcut.second.second);
|
||||
}
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->setValue("singleWindowMode", UISettings::values.single_window_mode);
|
||||
qt_config->setValue("fullscreen", UISettings::values.fullscreen);
|
||||
qt_config->setValue("displayTitleBars", UISettings::values.display_titlebar);
|
||||
qt_config->setValue("showFilterBar", UISettings::values.show_filter_bar);
|
||||
qt_config->setValue("showStatusBar", UISettings::values.show_status_bar);
|
||||
qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing);
|
||||
qt_config->setValue("firstStart", UISettings::values.first_start);
|
||||
qt_config->setValue("calloutFlags", UISettings::values.callout_flags);
|
||||
qt_config->setValue("showConsole", UISettings::values.show_console);
|
||||
qt_config->setValue("profileIndex", UISettings::values.profile_index);
|
||||
WriteSetting("singleWindowMode", UISettings::values.single_window_mode, true);
|
||||
WriteSetting("fullscreen", UISettings::values.fullscreen, false);
|
||||
WriteSetting("displayTitleBars", UISettings::values.display_titlebar, true);
|
||||
WriteSetting("showFilterBar", UISettings::values.show_filter_bar, true);
|
||||
WriteSetting("showStatusBar", UISettings::values.show_status_bar, true);
|
||||
WriteSetting("confirmClose", UISettings::values.confirm_before_closing, true);
|
||||
WriteSetting("firstStart", UISettings::values.first_start, true);
|
||||
WriteSetting("calloutFlags", UISettings::values.callout_flags, 0);
|
||||
WriteSetting("showConsole", UISettings::values.show_console, false);
|
||||
WriteSetting("profileIndex", UISettings::values.profile_index, 0);
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
QVariant Config::ReadSetting(const QString& name) const {
|
||||
return qt_config->value(name);
|
||||
}
|
||||
|
||||
QVariant Config::ReadSetting(const QString& name, const QVariant& default_value) const {
|
||||
QVariant result;
|
||||
if (qt_config->value(name + "/default", false).toBool()) {
|
||||
result = default_value;
|
||||
} else {
|
||||
result = qt_config->value(name, default_value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Config::WriteSetting(const QString& name, const QVariant& value) {
|
||||
qt_config->setValue(name, value);
|
||||
}
|
||||
|
||||
void Config::WriteSetting(const QString& name, const QVariant& value,
|
||||
const QVariant& default_value) {
|
||||
qt_config->setValue(name + "/default", value == default_value);
|
||||
qt_config->setValue(name, value);
|
||||
}
|
||||
|
||||
void Config::Reload() {
|
||||
ReadValues();
|
||||
// To apply default value changes
|
||||
SaveValues();
|
||||
Settings::Apply();
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,11 @@ private:
|
||||
void SaveMouseValues();
|
||||
void SaveTouchscreenValues();
|
||||
|
||||
QVariant ReadSetting(const QString& name) const;
|
||||
QVariant ReadSetting(const QString& name, const QVariant& default_value) const;
|
||||
void WriteSetting(const QString& name, const QVariant& value);
|
||||
void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value);
|
||||
|
||||
std::unique_ptr<QSettings> qt_config;
|
||||
std::string qt_config_loc;
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user