Compare commits
79 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57a4a2ae0f | ||
|
|
06ac6460d3 | ||
|
|
84d3cdf7d7 | ||
|
|
6788ebffc8 | ||
|
|
2d9546848e | ||
|
|
8bd17aa044 | ||
|
|
4e6c667586 | ||
|
|
ffe2e50458 | ||
|
|
6ed6129b4f | ||
|
|
9030a8259f | ||
|
|
fdf76a25ab | ||
|
|
e7850a7f11 | ||
|
|
c1ea6a39a0 | ||
|
|
0a923b4ab3 | ||
|
|
e8a21f5276 | ||
|
|
71c4e876ef | ||
|
|
a63295a872 | ||
|
|
a3734d7e31 | ||
|
|
aa59d77c3b | ||
|
|
6eddb60db0 | ||
|
|
3bfd199497 | ||
|
|
0f6dd7b64e | ||
|
|
8ebeb9ade2 | ||
|
|
2ad44a453f | ||
|
|
3350c0a779 | ||
|
|
1070c020db | ||
|
|
aa44eb639b | ||
|
|
a6c048920e | ||
|
|
0c28ab92e6 | ||
|
|
3f602dde0f | ||
|
|
f7ec0bcfc2 | ||
|
|
d870cc5ad7 | ||
|
|
8603f0f9b1 | ||
|
|
0aa824b12f | ||
|
|
037d9bdde3 | ||
|
|
633ce92908 | ||
|
|
4a84921b31 | ||
|
|
add8b1df68 | ||
|
|
0ea2771889 | ||
|
|
9ae680c639 | ||
|
|
46fdf8c819 | ||
|
|
4a4e87e971 | ||
|
|
e6f652ae12 | ||
|
|
6ce8de4b5f | ||
|
|
a0be7b3b92 | ||
|
|
45ef421b6b | ||
|
|
fedef7bda3 | ||
|
|
6ee0ba64c8 | ||
|
|
9909d40530 | ||
|
|
160fc63c72 | ||
|
|
78c803b4f3 | ||
|
|
1143923cdd | ||
|
|
d10dffed44 | ||
|
|
fbb82e61e3 | ||
|
|
69749a88cd | ||
|
|
8e510d5afa | ||
|
|
b7f331afa3 | ||
|
|
0209de123b | ||
|
|
e99a148628 | ||
|
|
c2d4c8b95e | ||
|
|
48a461a629 | ||
|
|
24e2e601d5 | ||
|
|
39e895c5ff | ||
|
|
d03ae881fd | ||
|
|
960057cba0 | ||
|
|
d41d85766f | ||
|
|
3293877456 | ||
|
|
fad20213e6 | ||
|
|
1f6571b3de | ||
|
|
71817afbe9 | ||
|
|
8b98f60e3c | ||
|
|
09ac66388c | ||
|
|
c8554d218b | ||
|
|
27ddbeb01c | ||
|
|
92ea1c32d6 | ||
|
|
254b1e3df7 | ||
|
|
1b2872eebc | ||
|
|
5ca63d0675 | ||
|
|
48e6f77c03 |
@@ -24,7 +24,7 @@ matrix:
|
||||
- os: osx
|
||||
env: NAME="macos build"
|
||||
sudo: false
|
||||
osx_image: xcode10
|
||||
osx_image: xcode10.1
|
||||
install: "./.travis/macos/deps.sh"
|
||||
script: "./.travis/macos/build.sh"
|
||||
after_success: "./.travis/macos/upload.sh"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
set -o pipefail
|
||||
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.13
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.14
|
||||
export Qt5_DIR=$(brew --prefix)/opt/qt5
|
||||
export UNICORNDIR=$(pwd)/externals/unicorn
|
||||
export PATH="/usr/local/opt/ccache/libexec:$PATH"
|
||||
|
||||
@@ -73,6 +73,7 @@ set(HASH_FILES
|
||||
"${VIDEO_CORE}/shader/decode/integer_set.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/integer_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/memory.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/texture.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/other.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/predicate_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/predicate_set_register.cpp"
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "common/ring_buffer.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#ifdef _WIN32
|
||||
#include <objbase.h>
|
||||
#endif
|
||||
|
||||
@@ -113,7 +113,7 @@ private:
|
||||
|
||||
CubebSink::CubebSink(std::string_view target_device_name) {
|
||||
// Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows
|
||||
#ifdef _MSC_VER
|
||||
#ifdef _WIN32
|
||||
com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
#endif
|
||||
|
||||
@@ -152,7 +152,7 @@ CubebSink::~CubebSink() {
|
||||
|
||||
cubeb_destroy(ctx);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#ifdef _WIN32
|
||||
if (SUCCEEDED(com_init_result)) {
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ private:
|
||||
cubeb_devid output_device{};
|
||||
std::vector<SinkStreamPtr> sink_streams;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#ifdef _WIN32
|
||||
u32 com_init_result = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -47,6 +47,7 @@ add_custom_command(OUTPUT scm_rev.cpp
|
||||
"${VIDEO_CORE}/shader/decode/integer_set.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/integer_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/memory.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/texture.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/other.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/predicate_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/predicate_set_register.cpp"
|
||||
|
||||
@@ -111,12 +111,6 @@
|
||||
template <std::size_t Position, std::size_t Bits, typename T>
|
||||
struct BitField {
|
||||
private:
|
||||
// We hide the copy assigment operator here, because the default copy
|
||||
// assignment would copy the full storage value, rather than just the bits
|
||||
// relevant to this particular bit field.
|
||||
// We don't delete it because we want BitField to be trivially copyable.
|
||||
constexpr BitField& operator=(const BitField&) = default;
|
||||
|
||||
// UnderlyingType is T for non-enum types and the underlying type of T if
|
||||
// T is an enumeration. Note that T is wrapped within an enable_if in the
|
||||
// former case to workaround compile errors which arise when using
|
||||
@@ -163,9 +157,13 @@ public:
|
||||
BitField(T val) = delete;
|
||||
BitField& operator=(T val) = delete;
|
||||
|
||||
// Force default constructor to be created
|
||||
// so that we can use this within unions
|
||||
constexpr BitField() = default;
|
||||
constexpr BitField() noexcept = default;
|
||||
|
||||
constexpr BitField(const BitField&) noexcept = default;
|
||||
constexpr BitField& operator=(const BitField&) noexcept = default;
|
||||
|
||||
constexpr BitField(BitField&&) noexcept = default;
|
||||
constexpr BitField& operator=(BitField&&) noexcept = default;
|
||||
|
||||
constexpr FORCE_INLINE operator T() const {
|
||||
return Value();
|
||||
|
||||
@@ -116,7 +116,7 @@ struct System::Impl {
|
||||
if (web_browser == nullptr)
|
||||
web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
|
||||
|
||||
auto main_process = Kernel::Process::Create(kernel, "main");
|
||||
auto main_process = Kernel::Process::Create(system, "main");
|
||||
kernel.MakeCurrentProcess(main_process.get());
|
||||
|
||||
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#endif
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/arm/unicorn/arm_unicorn.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
@@ -49,9 +50,9 @@ bool CpuBarrier::Rendezvous() {
|
||||
return false;
|
||||
}
|
||||
|
||||
Cpu::Cpu(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor,
|
||||
CpuBarrier& cpu_barrier, std::size_t core_index)
|
||||
: cpu_barrier{cpu_barrier}, core_timing{core_timing}, core_index{core_index} {
|
||||
Cpu::Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier,
|
||||
std::size_t core_index)
|
||||
: cpu_barrier{cpu_barrier}, core_timing{system.CoreTiming()}, core_index{core_index} {
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
arm_interface = std::make_unique<ARM_Dynarmic>(core_timing, exclusive_monitor, core_index);
|
||||
@@ -63,7 +64,7 @@ Cpu::Cpu(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor,
|
||||
arm_interface = std::make_unique<ARM_Unicorn>(core_timing);
|
||||
}
|
||||
|
||||
scheduler = std::make_unique<Kernel::Scheduler>(*arm_interface);
|
||||
scheduler = std::make_unique<Kernel::Scheduler>(system, *arm_interface);
|
||||
}
|
||||
|
||||
Cpu::~Cpu() = default;
|
||||
|
||||
@@ -15,6 +15,10 @@ namespace Kernel {
|
||||
class Scheduler;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
}
|
||||
@@ -45,8 +49,8 @@ private:
|
||||
|
||||
class Cpu {
|
||||
public:
|
||||
Cpu(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor,
|
||||
CpuBarrier& cpu_barrier, std::size_t core_index);
|
||||
Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier,
|
||||
std::size_t core_index);
|
||||
~Cpu();
|
||||
|
||||
void RunLoop(bool tight_loop = true);
|
||||
|
||||
@@ -27,8 +27,7 @@ void CpuCoreManager::Initialize(System& system) {
|
||||
exclusive_monitor = Cpu::MakeExclusiveMonitor(cores.size());
|
||||
|
||||
for (std::size_t index = 0; index < cores.size(); ++index) {
|
||||
cores[index] =
|
||||
std::make_unique<Cpu>(system.CoreTiming(), *exclusive_monitor, *barrier, index);
|
||||
cores[index] = std::make_unique<Cpu>(system, *exclusive_monitor, *barrier, index);
|
||||
}
|
||||
|
||||
// Create threads for CPU cores 1-3, and build thread_to_cpu map
|
||||
|
||||
@@ -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;
|
||||
@@ -350,7 +353,7 @@ public:
|
||||
template <class T>
|
||||
std::shared_ptr<T> PopIpcInterface() {
|
||||
ASSERT(context->Session()->IsDomain());
|
||||
ASSERT(context->GetDomainMessageHeader()->input_object_count > 0);
|
||||
ASSERT(context->GetDomainMessageHeader().input_object_count > 0);
|
||||
return context->GetDomainRequestHandler<T>(Pop<u32>() - 1);
|
||||
}
|
||||
};
|
||||
@@ -362,6 +365,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,6 +400,16 @@ 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>());
|
||||
|
||||
@@ -42,7 +42,21 @@ void WakeThreads(const std::vector<SharedPtr<Thread>>& waiting_threads, s32 num_
|
||||
AddressArbiter::AddressArbiter(Core::System& system) : system{system} {}
|
||||
AddressArbiter::~AddressArbiter() = default;
|
||||
|
||||
ResultCode AddressArbiter::SignalToAddress(VAddr address, s32 num_to_wake) {
|
||||
ResultCode AddressArbiter::SignalToAddress(VAddr address, SignalType type, s32 value,
|
||||
s32 num_to_wake) {
|
||||
switch (type) {
|
||||
case SignalType::Signal:
|
||||
return SignalToAddressOnly(address, num_to_wake);
|
||||
case SignalType::IncrementAndSignalIfEqual:
|
||||
return IncrementAndSignalToAddressIfEqual(address, value, num_to_wake);
|
||||
case SignalType::ModifyByWaitingCountAndSignalIfEqual:
|
||||
return ModifyByWaitingCountAndSignalToAddressIfEqual(address, value, num_to_wake);
|
||||
default:
|
||||
return ERR_INVALID_ENUM_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
ResultCode AddressArbiter::SignalToAddressOnly(VAddr address, s32 num_to_wake) {
|
||||
const std::vector<SharedPtr<Thread>> waiting_threads = GetThreadsWaitingOnAddress(address);
|
||||
WakeThreads(waiting_threads, num_to_wake);
|
||||
return RESULT_SUCCESS;
|
||||
@@ -60,7 +74,7 @@ ResultCode AddressArbiter::IncrementAndSignalToAddressIfEqual(VAddr address, s32
|
||||
}
|
||||
|
||||
Memory::Write32(address, static_cast<u32>(value + 1));
|
||||
return SignalToAddress(address, num_to_wake);
|
||||
return SignalToAddressOnly(address, num_to_wake);
|
||||
}
|
||||
|
||||
ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value,
|
||||
@@ -92,6 +106,20 @@ ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr a
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode AddressArbiter::WaitForAddress(VAddr address, ArbitrationType type, s32 value,
|
||||
s64 timeout_ns) {
|
||||
switch (type) {
|
||||
case ArbitrationType::WaitIfLessThan:
|
||||
return WaitForAddressIfLessThan(address, value, timeout_ns, false);
|
||||
case ArbitrationType::DecrementAndWaitIfLessThan:
|
||||
return WaitForAddressIfLessThan(address, value, timeout_ns, true);
|
||||
case ArbitrationType::WaitIfEqual:
|
||||
return WaitForAddressIfEqual(address, value, timeout_ns);
|
||||
default:
|
||||
return ERR_INVALID_ENUM_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout,
|
||||
bool should_decrement) {
|
||||
// Ensure that we can read the address.
|
||||
@@ -113,7 +141,7 @@ ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s6
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
return WaitForAddress(address, timeout);
|
||||
return WaitForAddressImpl(address, timeout);
|
||||
}
|
||||
|
||||
ResultCode AddressArbiter::WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout) {
|
||||
@@ -130,10 +158,10 @@ ResultCode AddressArbiter::WaitForAddressIfEqual(VAddr address, s32 value, s64 t
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
return WaitForAddress(address, timeout);
|
||||
return WaitForAddressImpl(address, timeout);
|
||||
}
|
||||
|
||||
ResultCode AddressArbiter::WaitForAddress(VAddr address, s64 timeout) {
|
||||
ResultCode AddressArbiter::WaitForAddressImpl(VAddr address, s64 timeout) {
|
||||
SharedPtr<Thread> current_thread = system.CurrentScheduler().GetCurrentThread();
|
||||
current_thread->SetArbiterWaitAddress(address);
|
||||
current_thread->SetStatus(ThreadStatus::WaitArb);
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/address_arbiter.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
|
||||
union ResultCode;
|
||||
|
||||
@@ -40,8 +42,15 @@ public:
|
||||
AddressArbiter(AddressArbiter&&) = default;
|
||||
AddressArbiter& operator=(AddressArbiter&&) = delete;
|
||||
|
||||
/// Signals an address being waited on with a particular signaling type.
|
||||
ResultCode SignalToAddress(VAddr address, SignalType type, s32 value, s32 num_to_wake);
|
||||
|
||||
/// Waits on an address with a particular arbitration type.
|
||||
ResultCode WaitForAddress(VAddr address, ArbitrationType type, s32 value, s64 timeout_ns);
|
||||
|
||||
private:
|
||||
/// Signals an address being waited on.
|
||||
ResultCode SignalToAddress(VAddr address, s32 num_to_wake);
|
||||
ResultCode SignalToAddressOnly(VAddr address, s32 num_to_wake);
|
||||
|
||||
/// Signals an address being waited on and increments its value if equal to the value argument.
|
||||
ResultCode IncrementAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake);
|
||||
@@ -59,9 +68,8 @@ public:
|
||||
/// Waits on an address if the value passed is equal to the argument value.
|
||||
ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout);
|
||||
|
||||
private:
|
||||
// Waits on the given address with a timeout in nanoseconds
|
||||
ResultCode WaitForAddress(VAddr address, s64 timeout);
|
||||
ResultCode WaitForAddressImpl(VAddr address, s64 timeout);
|
||||
|
||||
// Gets the threads waiting on an address.
|
||||
std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address) const;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -86,7 +86,7 @@ HLERequestContext::~HLERequestContext() = default;
|
||||
void HLERequestContext::ParseCommandBuffer(const HandleTable& handle_table, u32_le* src_cmdbuf,
|
||||
bool incoming) {
|
||||
IPC::RequestParser rp(src_cmdbuf);
|
||||
command_header = std::make_shared<IPC::CommandHeader>(rp.PopRaw<IPC::CommandHeader>());
|
||||
command_header = rp.PopRaw<IPC::CommandHeader>();
|
||||
|
||||
if (command_header->type == IPC::CommandType::Close) {
|
||||
// Close does not populate the rest of the IPC header
|
||||
@@ -95,8 +95,7 @@ void HLERequestContext::ParseCommandBuffer(const HandleTable& handle_table, u32_
|
||||
|
||||
// If handle descriptor is present, add size of it
|
||||
if (command_header->enable_handle_descriptor) {
|
||||
handle_descriptor_header =
|
||||
std::make_shared<IPC::HandleDescriptorHeader>(rp.PopRaw<IPC::HandleDescriptorHeader>());
|
||||
handle_descriptor_header = rp.PopRaw<IPC::HandleDescriptorHeader>();
|
||||
if (handle_descriptor_header->send_current_pid) {
|
||||
rp.Skip(2, false);
|
||||
}
|
||||
@@ -140,16 +139,15 @@ void HLERequestContext::ParseCommandBuffer(const HandleTable& handle_table, u32_
|
||||
// If this is an incoming message, only CommandType "Request" has a domain header
|
||||
// All outgoing domain messages have the domain header, if only incoming has it
|
||||
if (incoming || domain_message_header) {
|
||||
domain_message_header =
|
||||
std::make_shared<IPC::DomainMessageHeader>(rp.PopRaw<IPC::DomainMessageHeader>());
|
||||
domain_message_header = rp.PopRaw<IPC::DomainMessageHeader>();
|
||||
} else {
|
||||
if (Session()->IsDomain())
|
||||
if (Session()->IsDomain()) {
|
||||
LOG_WARNING(IPC, "Domain request has no DomainMessageHeader!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data_payload_header =
|
||||
std::make_shared<IPC::DataPayloadHeader>(rp.PopRaw<IPC::DataPayloadHeader>());
|
||||
data_payload_header = rp.PopRaw<IPC::DataPayloadHeader>();
|
||||
|
||||
data_payload_offset = rp.GetCurrentOffset();
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
@@ -168,12 +169,12 @@ public:
|
||||
return buffer_c_desciptors;
|
||||
}
|
||||
|
||||
const IPC::DomainMessageHeader* GetDomainMessageHeader() const {
|
||||
return domain_message_header.get();
|
||||
const IPC::DomainMessageHeader& GetDomainMessageHeader() const {
|
||||
return domain_message_header.value();
|
||||
}
|
||||
|
||||
bool HasDomainMessageHeader() const {
|
||||
return domain_message_header != nullptr;
|
||||
return domain_message_header.has_value();
|
||||
}
|
||||
|
||||
/// Helper function to read a buffer using the appropriate buffer descriptor
|
||||
@@ -272,10 +273,10 @@ private:
|
||||
boost::container::small_vector<SharedPtr<Object>, 8> copy_objects;
|
||||
boost::container::small_vector<std::shared_ptr<SessionRequestHandler>, 8> domain_objects;
|
||||
|
||||
std::shared_ptr<IPC::CommandHeader> command_header;
|
||||
std::shared_ptr<IPC::HandleDescriptorHeader> handle_descriptor_header;
|
||||
std::shared_ptr<IPC::DataPayloadHeader> data_payload_header;
|
||||
std::shared_ptr<IPC::DomainMessageHeader> domain_message_header;
|
||||
std::optional<IPC::CommandHeader> command_header;
|
||||
std::optional<IPC::HandleDescriptorHeader> handle_descriptor_header;
|
||||
std::optional<IPC::DataPayloadHeader> data_payload_header;
|
||||
std::optional<IPC::DomainMessageHeader> domain_message_header;
|
||||
std::vector<IPC::BufferDescriptorX> buffer_x_desciptors;
|
||||
std::vector<IPC::BufferDescriptorABW> buffer_a_desciptors;
|
||||
std::vector<IPC::BufferDescriptorABW> buffer_b_desciptors;
|
||||
|
||||
@@ -87,7 +87,7 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] int cycles_
|
||||
}
|
||||
|
||||
struct KernelCore::Impl {
|
||||
explicit Impl(Core::System& system) : address_arbiter{system}, system{system} {}
|
||||
explicit Impl(Core::System& system) : system{system} {}
|
||||
|
||||
void Initialize(KernelCore& kernel) {
|
||||
Shutdown();
|
||||
@@ -138,8 +138,6 @@ struct KernelCore::Impl {
|
||||
std::vector<SharedPtr<Process>> process_list;
|
||||
Process* current_process = nullptr;
|
||||
|
||||
Kernel::AddressArbiter address_arbiter;
|
||||
|
||||
SharedPtr<ResourceLimit> system_resource_limit;
|
||||
|
||||
Core::Timing::EventType* thread_wakeup_event_type = nullptr;
|
||||
@@ -192,14 +190,6 @@ const Process* KernelCore::CurrentProcess() const {
|
||||
return impl->current_process;
|
||||
}
|
||||
|
||||
AddressArbiter& KernelCore::AddressArbiter() {
|
||||
return impl->address_arbiter;
|
||||
}
|
||||
|
||||
const AddressArbiter& KernelCore::AddressArbiter() const {
|
||||
return impl->address_arbiter;
|
||||
}
|
||||
|
||||
void KernelCore::AddNamedPort(std::string name, SharedPtr<ClientPort> port) {
|
||||
impl->named_ports.emplace(std::move(name), std::move(port));
|
||||
}
|
||||
|
||||
@@ -75,12 +75,6 @@ public:
|
||||
/// Retrieves a const pointer to the current process.
|
||||
const Process* CurrentProcess() const;
|
||||
|
||||
/// Provides a reference to the kernel's address arbiter.
|
||||
Kernel::AddressArbiter& AddressArbiter();
|
||||
|
||||
/// Provides a const reference to the kernel's address arbiter.
|
||||
const Kernel::AddressArbiter& AddressArbiter() const;
|
||||
|
||||
/// Adds a port to the named port table
|
||||
void AddNamedPort(std::string name, SharedPtr<ClientPort> port);
|
||||
|
||||
|
||||
@@ -53,9 +53,10 @@ void SetupMainThread(Process& owner_process, KernelCore& kernel, VAddr entry_poi
|
||||
CodeSet::CodeSet() = default;
|
||||
CodeSet::~CodeSet() = default;
|
||||
|
||||
SharedPtr<Process> Process::Create(KernelCore& kernel, std::string&& name) {
|
||||
SharedPtr<Process> process(new Process(kernel));
|
||||
SharedPtr<Process> Process::Create(Core::System& system, std::string&& name) {
|
||||
auto& kernel = system.Kernel();
|
||||
|
||||
SharedPtr<Process> process(new Process(system));
|
||||
process->name = std::move(name);
|
||||
process->resource_limit = kernel.GetSystemResourceLimit();
|
||||
process->status = ProcessStatus::Created;
|
||||
@@ -132,7 +133,7 @@ void Process::PrepareForTermination() {
|
||||
if (thread->GetOwnerProcess() != this)
|
||||
continue;
|
||||
|
||||
if (thread == GetCurrentThread())
|
||||
if (thread == system.CurrentScheduler().GetCurrentThread())
|
||||
continue;
|
||||
|
||||
// TODO(Subv): When are the other running/ready threads terminated?
|
||||
@@ -144,7 +145,6 @@ void Process::PrepareForTermination() {
|
||||
}
|
||||
};
|
||||
|
||||
const auto& system = Core::System::GetInstance();
|
||||
stop_threads(system.Scheduler(0).GetThreadList());
|
||||
stop_threads(system.Scheduler(1).GetThreadList());
|
||||
stop_threads(system.Scheduler(2).GetThreadList());
|
||||
@@ -227,14 +227,12 @@ void Process::LoadModule(CodeSet module_, VAddr base_addr) {
|
||||
MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeMutable);
|
||||
|
||||
// Clear instruction cache in CPU JIT
|
||||
Core::System::GetInstance().ArmInterface(0).ClearInstructionCache();
|
||||
Core::System::GetInstance().ArmInterface(1).ClearInstructionCache();
|
||||
Core::System::GetInstance().ArmInterface(2).ClearInstructionCache();
|
||||
Core::System::GetInstance().ArmInterface(3).ClearInstructionCache();
|
||||
system.InvalidateCpuInstructionCaches();
|
||||
}
|
||||
|
||||
Kernel::Process::Process(KernelCore& kernel) : WaitObject{kernel} {}
|
||||
Kernel::Process::~Process() {}
|
||||
Process::Process(Core::System& system)
|
||||
: WaitObject{system.Kernel()}, address_arbiter{system}, system{system} {}
|
||||
Process::~Process() = default;
|
||||
|
||||
void Process::Acquire(Thread* thread) {
|
||||
ASSERT_MSG(!ShouldWait(thread), "Object unavailable!");
|
||||
|
||||
@@ -12,12 +12,17 @@
|
||||
#include <vector>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/address_arbiter.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/process_capability.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/hle/kernel/wait_object.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
class ProgramMetadata;
|
||||
}
|
||||
@@ -116,7 +121,7 @@ public:
|
||||
|
||||
static constexpr std::size_t RANDOM_ENTROPY_SIZE = 4;
|
||||
|
||||
static SharedPtr<Process> Create(KernelCore& kernel, std::string&& name);
|
||||
static SharedPtr<Process> Create(Core::System& system, std::string&& name);
|
||||
|
||||
std::string GetTypeName() const override {
|
||||
return "Process";
|
||||
@@ -150,6 +155,16 @@ public:
|
||||
return handle_table;
|
||||
}
|
||||
|
||||
/// Gets a reference to the process' address arbiter.
|
||||
AddressArbiter& GetAddressArbiter() {
|
||||
return address_arbiter;
|
||||
}
|
||||
|
||||
/// Gets a const reference to the process' address arbiter.
|
||||
const AddressArbiter& GetAddressArbiter() const {
|
||||
return address_arbiter;
|
||||
}
|
||||
|
||||
/// Gets the current status of the process
|
||||
ProcessStatus GetStatus() const {
|
||||
return status;
|
||||
@@ -251,7 +266,7 @@ public:
|
||||
void FreeTLSSlot(VAddr tls_address);
|
||||
|
||||
private:
|
||||
explicit Process(KernelCore& kernel);
|
||||
explicit Process(Core::System& system);
|
||||
~Process() override;
|
||||
|
||||
/// Checks if the specified thread should wait until this process is available.
|
||||
@@ -309,9 +324,16 @@ private:
|
||||
/// Per-process handle table for storing created object handles in.
|
||||
HandleTable handle_table;
|
||||
|
||||
/// Per-process address arbiter.
|
||||
AddressArbiter address_arbiter;
|
||||
|
||||
/// Random values for svcGetInfo RandomEntropy
|
||||
std::array<u64, RANDOM_ENTROPY_SIZE> random_entropy;
|
||||
|
||||
/// System context
|
||||
Core::System& system;
|
||||
|
||||
/// Name of this process
|
||||
std::string name;
|
||||
};
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ namespace Kernel {
|
||||
|
||||
std::mutex Scheduler::scheduler_mutex;
|
||||
|
||||
Scheduler::Scheduler(Core::ARM_Interface& cpu_core) : cpu_core(cpu_core) {}
|
||||
Scheduler::Scheduler(Core::System& system, Core::ARM_Interface& cpu_core)
|
||||
: cpu_core{cpu_core}, system{system} {}
|
||||
|
||||
Scheduler::~Scheduler() {
|
||||
for (auto& thread : thread_list) {
|
||||
@@ -61,7 +62,7 @@ Thread* Scheduler::PopNextReadyThread() {
|
||||
|
||||
void Scheduler::SwitchContext(Thread* new_thread) {
|
||||
Thread* const previous_thread = GetCurrentThread();
|
||||
Process* const previous_process = Core::CurrentProcess();
|
||||
Process* const previous_process = system.Kernel().CurrentProcess();
|
||||
|
||||
UpdateLastContextSwitchTime(previous_thread, previous_process);
|
||||
|
||||
@@ -94,8 +95,8 @@ void Scheduler::SwitchContext(Thread* new_thread) {
|
||||
|
||||
auto* const thread_owner_process = current_thread->GetOwnerProcess();
|
||||
if (previous_process != thread_owner_process) {
|
||||
Core::System::GetInstance().Kernel().MakeCurrentProcess(thread_owner_process);
|
||||
SetCurrentPageTable(&Core::CurrentProcess()->VMManager().page_table);
|
||||
system.Kernel().MakeCurrentProcess(thread_owner_process);
|
||||
SetCurrentPageTable(&thread_owner_process->VMManager().page_table);
|
||||
}
|
||||
|
||||
cpu_core.LoadContext(new_thread->GetContext());
|
||||
@@ -111,7 +112,7 @@ void Scheduler::SwitchContext(Thread* new_thread) {
|
||||
|
||||
void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) {
|
||||
const u64 prev_switch_ticks = last_context_switch_time;
|
||||
const u64 most_recent_switch_ticks = Core::System::GetInstance().CoreTiming().GetTicks();
|
||||
const u64 most_recent_switch_ticks = system.CoreTiming().GetTicks();
|
||||
const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks;
|
||||
|
||||
if (thread != nullptr) {
|
||||
@@ -223,8 +224,7 @@ void Scheduler::YieldWithLoadBalancing(Thread* thread) {
|
||||
// Take the first non-nullptr one
|
||||
for (unsigned cur_core = 0; cur_core < Core::NUM_CPU_CORES; ++cur_core) {
|
||||
const auto res =
|
||||
Core::System::GetInstance().CpuCore(cur_core).Scheduler().GetNextSuggestedThread(
|
||||
core, priority);
|
||||
system.CpuCore(cur_core).Scheduler().GetNextSuggestedThread(core, priority);
|
||||
|
||||
// If scheduler provides a suggested thread
|
||||
if (res != nullptr) {
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
|
||||
namespace Core {
|
||||
class ARM_Interface;
|
||||
}
|
||||
class System;
|
||||
} // namespace Core
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
@@ -21,7 +22,7 @@ class Process;
|
||||
|
||||
class Scheduler final {
|
||||
public:
|
||||
explicit Scheduler(Core::ARM_Interface& cpu_core);
|
||||
explicit Scheduler(Core::System& system, Core::ARM_Interface& cpu_core);
|
||||
~Scheduler();
|
||||
|
||||
/// Returns whether there are any threads that are ready to run.
|
||||
@@ -162,6 +163,7 @@ private:
|
||||
Core::ARM_Interface& cpu_core;
|
||||
u64 last_context_switch_time = 0;
|
||||
|
||||
Core::System& system;
|
||||
static std::mutex scheduler_mutex;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -92,41 +92,42 @@ std::size_t ServerSession::NumDomainRequestHandlers() const {
|
||||
}
|
||||
|
||||
ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& context) {
|
||||
auto* const domain_message_header = context.GetDomainMessageHeader();
|
||||
if (domain_message_header) {
|
||||
// Set domain handlers in HLE context, used for domain objects (IPC interfaces) as inputs
|
||||
context.SetDomainRequestHandlers(domain_request_handlers);
|
||||
|
||||
// If there is a DomainMessageHeader, then this is CommandType "Request"
|
||||
const u32 object_id{context.GetDomainMessageHeader()->object_id};
|
||||
switch (domain_message_header->command) {
|
||||
case IPC::DomainMessageHeader::CommandType::SendMessage:
|
||||
if (object_id > domain_request_handlers.size()) {
|
||||
LOG_CRITICAL(IPC,
|
||||
"object_id {} is too big! This probably means a recent service call "
|
||||
"to {} needed to return a new interface!",
|
||||
object_id, name);
|
||||
UNREACHABLE();
|
||||
return RESULT_SUCCESS; // Ignore error if asserts are off
|
||||
}
|
||||
return domain_request_handlers[object_id - 1]->HandleSyncRequest(context);
|
||||
|
||||
case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: {
|
||||
LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x{:08X}", object_id);
|
||||
|
||||
domain_request_handlers[object_id - 1] = nullptr;
|
||||
|
||||
IPC::ResponseBuilder rb{context, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_CRITICAL(IPC, "Unknown domain command={}",
|
||||
static_cast<int>(domain_message_header->command.Value()));
|
||||
ASSERT(false);
|
||||
if (!context.HasDomainMessageHeader()) {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
// Set domain handlers in HLE context, used for domain objects (IPC interfaces) as inputs
|
||||
context.SetDomainRequestHandlers(domain_request_handlers);
|
||||
|
||||
// If there is a DomainMessageHeader, then this is CommandType "Request"
|
||||
const auto& domain_message_header = context.GetDomainMessageHeader();
|
||||
const u32 object_id{domain_message_header.object_id};
|
||||
switch (domain_message_header.command) {
|
||||
case IPC::DomainMessageHeader::CommandType::SendMessage:
|
||||
if (object_id > domain_request_handlers.size()) {
|
||||
LOG_CRITICAL(IPC,
|
||||
"object_id {} is too big! This probably means a recent service call "
|
||||
"to {} needed to return a new interface!",
|
||||
object_id, name);
|
||||
UNREACHABLE();
|
||||
return RESULT_SUCCESS; // Ignore error if asserts are off
|
||||
}
|
||||
return domain_request_handlers[object_id - 1]->HandleSyncRequest(context);
|
||||
|
||||
case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: {
|
||||
LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x{:08X}", object_id);
|
||||
|
||||
domain_request_handlers[object_id - 1] = nullptr;
|
||||
|
||||
IPC::ResponseBuilder rb{context, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_CRITICAL(IPC, "Unknown domain command={}",
|
||||
static_cast<int>(domain_message_header.command.Value()));
|
||||
ASSERT(false);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -1479,21 +1479,10 @@ static ResultCode WaitForAddress(VAddr address, u32 type, s32 value, s64 timeout
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
auto& address_arbiter = Core::System::GetInstance().Kernel().AddressArbiter();
|
||||
switch (static_cast<AddressArbiter::ArbitrationType>(type)) {
|
||||
case AddressArbiter::ArbitrationType::WaitIfLessThan:
|
||||
return address_arbiter.WaitForAddressIfLessThan(address, value, timeout, false);
|
||||
case AddressArbiter::ArbitrationType::DecrementAndWaitIfLessThan:
|
||||
return address_arbiter.WaitForAddressIfLessThan(address, value, timeout, true);
|
||||
case AddressArbiter::ArbitrationType::WaitIfEqual:
|
||||
return address_arbiter.WaitForAddressIfEqual(address, value, timeout);
|
||||
default:
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Invalid arbitration type, expected WaitIfLessThan, DecrementAndWaitIfLessThan "
|
||||
"or WaitIfEqual but got {}",
|
||||
type);
|
||||
return ERR_INVALID_ENUM_VALUE;
|
||||
}
|
||||
const auto arbitration_type = static_cast<AddressArbiter::ArbitrationType>(type);
|
||||
auto& address_arbiter =
|
||||
Core::System::GetInstance().Kernel().CurrentProcess()->GetAddressArbiter();
|
||||
return address_arbiter.WaitForAddress(address, arbitration_type, value, timeout);
|
||||
}
|
||||
|
||||
// Signals to an address (via Address Arbiter)
|
||||
@@ -1511,22 +1500,10 @@ static ResultCode SignalToAddress(VAddr address, u32 type, s32 value, s32 num_to
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
auto& address_arbiter = Core::System::GetInstance().Kernel().AddressArbiter();
|
||||
switch (static_cast<AddressArbiter::SignalType>(type)) {
|
||||
case AddressArbiter::SignalType::Signal:
|
||||
return address_arbiter.SignalToAddress(address, num_to_wake);
|
||||
case AddressArbiter::SignalType::IncrementAndSignalIfEqual:
|
||||
return address_arbiter.IncrementAndSignalToAddressIfEqual(address, value, num_to_wake);
|
||||
case AddressArbiter::SignalType::ModifyByWaitingCountAndSignalIfEqual:
|
||||
return address_arbiter.ModifyByWaitingCountAndSignalToAddressIfEqual(address, value,
|
||||
num_to_wake);
|
||||
default:
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Invalid signal type, expected Signal, IncrementAndSignalIfEqual "
|
||||
"or ModifyByWaitingCountAndSignalIfEqual but got {}",
|
||||
type);
|
||||
return ERR_INVALID_ENUM_VALUE;
|
||||
}
|
||||
const auto signal_type = static_cast<AddressArbiter::SignalType>(type);
|
||||
auto& address_arbiter =
|
||||
Core::System::GetInstance().Kernel().CurrentProcess()->GetAddressArbiter();
|
||||
return address_arbiter.SignalToAddress(address, signal_type, value, num_to_wake);
|
||||
}
|
||||
|
||||
/// This returns the total CPU ticks elapsed since the CPU was powered-on
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -9,43 +9,32 @@
|
||||
|
||||
#include <opus.h>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/service/audio/hwopus.h"
|
||||
|
||||
namespace Service::Audio {
|
||||
|
||||
namespace {
|
||||
struct OpusDeleter {
|
||||
void operator()(void* ptr) const {
|
||||
operator delete(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> {
|
||||
using OpusDecoderPtr = std::unique_ptr<OpusDecoder, OpusDeleter>;
|
||||
|
||||
struct OpusPacketHeader {
|
||||
// Packet size in bytes.
|
||||
u32_be size;
|
||||
// Indicates the final range of the codec's entropy coder.
|
||||
u32_be final_range;
|
||||
};
|
||||
static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size");
|
||||
|
||||
class OpusDecoderStateBase {
|
||||
public:
|
||||
IHardwareOpusDecoderManager(std::unique_ptr<OpusDecoder, OpusDeleter> decoder, u32 sample_rate,
|
||||
u32 channel_count)
|
||||
: ServiceFramework("IHardwareOpusDecoderManager"), decoder(std::move(decoder)),
|
||||
sample_rate(sample_rate), channel_count(channel_count) {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"},
|
||||
{1, nullptr, "SetContext"},
|
||||
{2, nullptr, "DecodeInterleavedForMultiStreamOld"},
|
||||
{3, nullptr, "SetContextForMultiStream"},
|
||||
{4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"},
|
||||
{5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"},
|
||||
{6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"},
|
||||
{7, nullptr, "DecodeInterleavedForMultiStream"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
/// Describes extra behavior that may be asked of the decoding context.
|
||||
enum class ExtraBehavior {
|
||||
/// No extra behavior.
|
||||
@@ -55,30 +44,36 @@ private:
|
||||
ResetContext,
|
||||
};
|
||||
|
||||
void DecodeInterleavedOld(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
enum class PerfTime {
|
||||
Disabled,
|
||||
Enabled,
|
||||
};
|
||||
|
||||
DecodeInterleavedHelper(ctx, nullptr, ExtraBehavior::None);
|
||||
}
|
||||
|
||||
void DecodeInterleavedWithPerfOld(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
u64 performance = 0;
|
||||
DecodeInterleavedHelper(ctx, &performance, ExtraBehavior::None);
|
||||
}
|
||||
|
||||
void DecodeInterleaved(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto extra_behavior =
|
||||
rp.Pop<bool>() ? ExtraBehavior::ResetContext : ExtraBehavior::None;
|
||||
|
||||
u64 performance = 0;
|
||||
DecodeInterleavedHelper(ctx, &performance, extra_behavior);
|
||||
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} {}
|
||||
|
||||
void DecodeInterleaved(Kernel::HLERequestContext& ctx, PerfTime perf_time,
|
||||
ExtraBehavior extra_behavior) override {
|
||||
if (perf_time == PerfTime::Disabled) {
|
||||
DecodeInterleavedHelper(ctx, nullptr, extra_behavior);
|
||||
} else {
|
||||
u64 performance = 0;
|
||||
DecodeInterleavedHelper(ctx, &performance, extra_behavior);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void DecodeInterleavedHelper(Kernel::HLERequestContext& ctx, u64* performance,
|
||||
ExtraBehavior extra_behavior) {
|
||||
u32 consumed = 0;
|
||||
@@ -89,8 +84,7 @@ private:
|
||||
ResetDecoderContext();
|
||||
}
|
||||
|
||||
if (!Decoder_DecodeInterleaved(consumed, sample_count, ctx.ReadBuffer(), samples,
|
||||
performance)) {
|
||||
if (!DecodeOpusData(consumed, sample_count, ctx.ReadBuffer(), samples, performance)) {
|
||||
LOG_ERROR(Audio, "Failed to decode opus data");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(ogniK): Use correct error code
|
||||
@@ -109,27 +103,27 @@ private:
|
||||
ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16));
|
||||
}
|
||||
|
||||
bool Decoder_DecodeInterleaved(u32& consumed, u32& sample_count, const std::vector<u8>& input,
|
||||
std::vector<opus_int16>& output, u64* out_performance_time) {
|
||||
bool DecodeOpusData(u32& consumed, u32& sample_count, const std::vector<u8>& input,
|
||||
std::vector<opus_int16>& output, u64* out_performance_time) const {
|
||||
const auto start_time = std::chrono::high_resolution_clock::now();
|
||||
const std::size_t raw_output_sz = output.size() * sizeof(opus_int16);
|
||||
if (sizeof(OpusHeader) > input.size()) {
|
||||
if (sizeof(OpusPacketHeader) > input.size()) {
|
||||
LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}",
|
||||
sizeof(OpusHeader), input.size());
|
||||
sizeof(OpusPacketHeader), input.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
OpusHeader hdr{};
|
||||
std::memcpy(&hdr, input.data(), sizeof(OpusHeader));
|
||||
if (sizeof(OpusHeader) + static_cast<u32>(hdr.sz) > input.size()) {
|
||||
OpusPacketHeader hdr{};
|
||||
std::memcpy(&hdr, input.data(), sizeof(OpusPacketHeader));
|
||||
if (sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size) > input.size()) {
|
||||
LOG_ERROR(Audio, "Input does not fit in the opus header size. data_sz={}, input_sz={}",
|
||||
sizeof(OpusHeader) + static_cast<u32>(hdr.sz), input.size());
|
||||
sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size), input.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto frame = input.data() + sizeof(OpusHeader);
|
||||
const auto frame = input.data() + sizeof(OpusPacketHeader);
|
||||
const auto decoded_sample_count = opus_packet_get_nb_samples(
|
||||
frame, static_cast<opus_int32>(input.size() - sizeof(OpusHeader)),
|
||||
frame, static_cast<opus_int32>(input.size() - sizeof(OpusPacketHeader)),
|
||||
static_cast<opus_int32>(sample_rate));
|
||||
if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) {
|
||||
LOG_ERROR(
|
||||
@@ -141,18 +135,18 @@ 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.sz, output.data(), frame_size, 0);
|
||||
opus_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, "
|
||||
"output_sample_count={}, frame_size={}, data_sz_from_hdr={}",
|
||||
out_sample_count, frame_size, static_cast<u32>(hdr.sz));
|
||||
out_sample_count, frame_size, static_cast<u32>(hdr.size));
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto end_time = std::chrono::high_resolution_clock::now() - start_time;
|
||||
sample_count = out_sample_count;
|
||||
consumed = static_cast<u32>(sizeof(OpusHeader) + hdr.sz);
|
||||
consumed = static_cast<u32>(sizeof(OpusPacketHeader) + hdr.size);
|
||||
if (out_performance_time != nullptr) {
|
||||
*out_performance_time =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count();
|
||||
@@ -167,21 +161,66 @@ private:
|
||||
opus_decoder_ctl(decoder.get(), OPUS_RESET_STATE);
|
||||
}
|
||||
|
||||
struct OpusHeader {
|
||||
u32_be sz; // Needs to be BE for some odd reason
|
||||
INSERT_PADDING_WORDS(1);
|
||||
};
|
||||
static_assert(sizeof(OpusHeader) == 0x8, "OpusHeader is an invalid size");
|
||||
|
||||
std::unique_ptr<OpusDecoder, OpusDeleter> decoder;
|
||||
OpusDecoderPtr decoder;
|
||||
u32 sample_rate;
|
||||
u32 channel_count;
|
||||
};
|
||||
|
||||
static std::size_t WorkerBufferSize(u32 channel_count) {
|
||||
class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> {
|
||||
public:
|
||||
explicit IHardwareOpusDecoderManager(std::unique_ptr<OpusDecoderStateBase> decoder_state)
|
||||
: ServiceFramework("IHardwareOpusDecoderManager"), decoder_state{std::move(decoder_state)} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"},
|
||||
{1, nullptr, "SetContext"},
|
||||
{2, nullptr, "DecodeInterleavedForMultiStreamOld"},
|
||||
{3, nullptr, "SetContextForMultiStream"},
|
||||
{4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"},
|
||||
{5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"},
|
||||
{6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"},
|
||||
{7, nullptr, "DecodeInterleavedForMultiStream"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
void DecodeInterleavedOld(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Disabled,
|
||||
OpusDecoderStateBase::ExtraBehavior::None);
|
||||
}
|
||||
|
||||
void DecodeInterleavedWithPerfOld(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Enabled,
|
||||
OpusDecoderStateBase::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;
|
||||
|
||||
decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Enabled,
|
||||
extra_behavior);
|
||||
}
|
||||
|
||||
std::unique_ptr<OpusDecoderStateBase> 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));
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
void HwOpus::GetWorkBufferSize(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
@@ -220,8 +259,7 @@ 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");
|
||||
|
||||
std::unique_ptr<OpusDecoder, OpusDeleter> decoder{
|
||||
static_cast<OpusDecoder*>(operator new(worker_sz))};
|
||||
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);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
@@ -232,8 +270,8 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IHardwareOpusDecoderManager>(std::move(decoder), sample_rate,
|
||||
channel_count);
|
||||
rb.PushIpcInterface<IHardwareOpusDecoderManager>(
|
||||
std::make_unique<OpusDecoderState>(std::move(decoder), sample_rate, channel_count));
|
||||
}
|
||||
|
||||
HwOpus::HwOpus() : ServiceFramework("hwopus") {
|
||||
|
||||
@@ -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"
|
||||
@@ -76,7 +75,8 @@ namespace Service {
|
||||
* Creates a function string for logging, complete with the name (or header code, depending
|
||||
* on what's passed in) the port name, and all the cmd_buff arguments.
|
||||
*/
|
||||
[[maybe_unused]] static std::string MakeFunctionString(const char* name, const char* port_name,
|
||||
[[maybe_unused]] static std::string MakeFunctionString(std::string_view name,
|
||||
std::string_view port_name,
|
||||
const u32* cmd_buff) {
|
||||
// Number of params == bits 0-5 + bits 6-11
|
||||
int num_params = (cmd_buff[0] & 0x3F) + ((cmd_buff[0] >> 6) & 0x3F);
|
||||
@@ -158,9 +158,7 @@ void ServiceFrameworkBase::InvokeRequest(Kernel::HLERequestContext& ctx) {
|
||||
return ReportUnimplementedFunction(ctx, info);
|
||||
}
|
||||
|
||||
LOG_TRACE(
|
||||
Service, "{}",
|
||||
MakeFunctionString(info->name, GetServiceName().c_str(), ctx.CommandBuffer()).c_str());
|
||||
LOG_TRACE(Service, "{}", MakeFunctionString(info->name, GetServiceName(), ctx.CommandBuffer()));
|
||||
handler_invoker(this, info->handler_callback, ctx);
|
||||
}
|
||||
|
||||
@@ -169,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
|
||||
|
||||
@@ -91,7 +91,10 @@ void LogSettings() {
|
||||
LogSetting("Renderer_UseResolutionFactor", Settings::values.resolution_factor);
|
||||
LogSetting("Renderer_UseFrameLimit", Settings::values.use_frame_limit);
|
||||
LogSetting("Renderer_FrameLimit", Settings::values.frame_limit);
|
||||
LogSetting("Renderer_UseDiskShaderCache", Settings::values.use_disk_shader_cache);
|
||||
LogSetting("Renderer_UseAccurateGpuEmulation", Settings::values.use_accurate_gpu_emulation);
|
||||
LogSetting("Renderer_UseAsynchronousGpuEmulation",
|
||||
Settings::values.use_asynchronous_gpu_emulation);
|
||||
LogSetting("Audio_OutputEngine", Settings::values.sink_id);
|
||||
LogSetting("Audio_EnableAudioStretching", Settings::values.enable_audio_stretching);
|
||||
LogSetting("Audio_OutputDevice", Settings::values.audio_device_id);
|
||||
|
||||
@@ -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
|
||||
|
||||
669
src/input_common/sdl/sdl_impl.cpp
Normal file
669
src/input_common/sdl/sdl_impl.cpp
Normal file
@@ -0,0 +1,669 @@
|
||||
// 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([&] {
|
||||
using namespace std::chrono_literals;
|
||||
SDL_Event event;
|
||||
while (initialized) {
|
||||
SDL_PumpEvents();
|
||||
std::this_thread::sleep_for(std::chrono::duration(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
|
||||
@@ -15,7 +15,7 @@ namespace ArmTests {
|
||||
TestEnvironment::TestEnvironment(bool mutable_memory_)
|
||||
: mutable_memory(mutable_memory_),
|
||||
test_memory(std::make_shared<TestMemory>(this)), kernel{Core::System::GetInstance()} {
|
||||
auto process = Kernel::Process::Create(kernel, "");
|
||||
auto process = Kernel::Process::Create(Core::System::GetInstance(), "");
|
||||
kernel.MakeCurrentProcess(process.get());
|
||||
page_table = &process->VMManager().page_table;
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ add_library(video_core STATIC
|
||||
shader/decode/hfma2.cpp
|
||||
shader/decode/conversion.cpp
|
||||
shader/decode/memory.cpp
|
||||
shader/decode/texture.cpp
|
||||
shader/decode/float_set_predicate.cpp
|
||||
shader/decode/integer_set_predicate.cpp
|
||||
shader/decode/half_set_predicate.cpp
|
||||
@@ -112,6 +113,8 @@ add_library(video_core STATIC
|
||||
if (ENABLE_VULKAN)
|
||||
target_sources(video_core PRIVATE
|
||||
renderer_vulkan/declarations.h
|
||||
renderer_vulkan/maxwell_to_vk.cpp
|
||||
renderer_vulkan/maxwell_to_vk.h
|
||||
renderer_vulkan/vk_buffer_cache.cpp
|
||||
renderer_vulkan/vk_buffer_cache.h
|
||||
renderer_vulkan/vk_device.cpp
|
||||
@@ -120,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
|
||||
|
||||
@@ -324,11 +324,11 @@ enum class TextureQueryType : u64 {
|
||||
|
||||
enum class TextureProcessMode : u64 {
|
||||
None = 0,
|
||||
LZ = 1, // Unknown, appears to be the same as none.
|
||||
LZ = 1, // Load LOD of zero.
|
||||
LB = 2, // Load Bias.
|
||||
LL = 3, // Load LOD (LevelOfDetail)
|
||||
LBA = 6, // Load Bias. The A is unknown, does not appear to differ with LB
|
||||
LLA = 7 // Load LOD. The A is unknown, does not appear to differ with LL
|
||||
LL = 3, // Load LOD.
|
||||
LBA = 6, // Load Bias. The A is unknown, does not appear to differ with LB.
|
||||
LLA = 7 // Load LOD. The A is unknown, does not appear to differ with LL.
|
||||
};
|
||||
|
||||
enum class TextureMiscMode : u64 {
|
||||
@@ -1445,6 +1445,7 @@ public:
|
||||
Flow,
|
||||
Synch,
|
||||
Memory,
|
||||
Texture,
|
||||
FloatSet,
|
||||
FloatSetPredicate,
|
||||
IntegerSet,
|
||||
@@ -1575,14 +1576,14 @@ private:
|
||||
INST("1110111101010---", Id::ST_L, Type::Memory, "ST_L"),
|
||||
INST("1110111011010---", Id::LDG, Type::Memory, "LDG"),
|
||||
INST("1110111011011---", Id::STG, Type::Memory, "STG"),
|
||||
INST("110000----111---", Id::TEX, Type::Memory, "TEX"),
|
||||
INST("1101111101001---", Id::TXQ, Type::Memory, "TXQ"),
|
||||
INST("1101-00---------", Id::TEXS, Type::Memory, "TEXS"),
|
||||
INST("1101101---------", Id::TLDS, Type::Memory, "TLDS"),
|
||||
INST("110010----111---", Id::TLD4, Type::Memory, "TLD4"),
|
||||
INST("1101111100------", Id::TLD4S, Type::Memory, "TLD4S"),
|
||||
INST("110111110110----", Id::TMML_B, Type::Memory, "TMML_B"),
|
||||
INST("1101111101011---", Id::TMML, Type::Memory, "TMML"),
|
||||
INST("110000----111---", Id::TEX, Type::Texture, "TEX"),
|
||||
INST("1101111101001---", Id::TXQ, Type::Texture, "TXQ"),
|
||||
INST("1101-00---------", Id::TEXS, Type::Texture, "TEXS"),
|
||||
INST("1101101---------", Id::TLDS, Type::Texture, "TLDS"),
|
||||
INST("110010----111---", Id::TLD4, Type::Texture, "TLD4"),
|
||||
INST("1101111100------", Id::TLD4S, Type::Texture, "TLD4S"),
|
||||
INST("110111110110----", Id::TMML_B, Type::Texture, "TMML_B"),
|
||||
INST("1101111101011---", Id::TMML, Type::Texture, "TMML"),
|
||||
INST("111000110000----", Id::EXIT, Type::Trivial, "EXIT"),
|
||||
INST("11100000--------", Id::IPA, Type::Trivial, "IPA"),
|
||||
INST("1111101111100---", Id::OUT_R, Type::Trivial, "OUT_R"),
|
||||
|
||||
@@ -123,7 +123,7 @@ class GPU {
|
||||
public:
|
||||
explicit GPU(Core::System& system, VideoCore::RendererBase& renderer);
|
||||
|
||||
~GPU();
|
||||
virtual ~GPU();
|
||||
|
||||
struct MethodCall {
|
||||
u32 method{};
|
||||
|
||||
@@ -21,7 +21,7 @@ class ThreadManager;
|
||||
class GPUAsynch : public Tegra::GPU {
|
||||
public:
|
||||
explicit GPUAsynch(Core::System& system, VideoCore::RendererBase& renderer);
|
||||
~GPUAsynch();
|
||||
~GPUAsynch() override;
|
||||
|
||||
void PushGPUEntries(Tegra::CommandList&& entries) override;
|
||||
void SwapBuffers(
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace VideoCommon {
|
||||
class GPUSynch : public Tegra::GPU {
|
||||
public:
|
||||
explicit GPUSynch(Core::System& system, VideoCore::RendererBase& renderer);
|
||||
~GPUSynch();
|
||||
~GPUSynch() override;
|
||||
|
||||
void PushGPUEntries(Tegra::CommandList&& entries) override;
|
||||
void SwapBuffers(
|
||||
|
||||
@@ -113,9 +113,6 @@ public:
|
||||
/// Notify rasterizer that any caches of the specified region should be flushed and invalidated
|
||||
void FlushAndInvalidateRegion(VAddr addr, u64 size);
|
||||
|
||||
/// Waits the caller until the GPU thread is idle, used for synchronization
|
||||
void WaitForIdle();
|
||||
|
||||
private:
|
||||
/// Pushes a command to be executed by the GPU thread
|
||||
void PushCommand(CommandData&& command_data, bool wait_for_idle, bool allow_on_cpu);
|
||||
@@ -127,10 +124,10 @@ private:
|
||||
|
||||
private:
|
||||
SynchState state;
|
||||
std::thread thread;
|
||||
std::thread::id thread_id;
|
||||
VideoCore::RendererBase& renderer;
|
||||
Tegra::DmaPusher& dma_pusher;
|
||||
std::thread thread;
|
||||
std::thread::id thread_id;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon::GPUThread
|
||||
|
||||
@@ -16,12 +16,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*, VAddr);
|
||||
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, VAddr addr) {
|
||||
constexpr u32 bytes_per_pixel = GetBytesPerPixel(format);
|
||||
|
||||
// With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
|
||||
@@ -42,142 +42,138 @@ static void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth
|
||||
}
|
||||
|
||||
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 +187,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 +282,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, VAddr 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, VAddr 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
|
||||
|
||||
@@ -57,8 +57,8 @@ GlobalRegion GlobalRegionCacheOpenGL::GetUncachedGlobalRegion(VAddr addr, u32 si
|
||||
return region;
|
||||
}
|
||||
|
||||
void GlobalRegionCacheOpenGL::ReserveGlobalRegion(const GlobalRegion& region) {
|
||||
reserve[region->GetAddr()] = region;
|
||||
void GlobalRegionCacheOpenGL::ReserveGlobalRegion(GlobalRegion region) {
|
||||
reserve.insert_or_assign(region->GetAddr(), std::move(region));
|
||||
}
|
||||
|
||||
GlobalRegionCacheOpenGL::GlobalRegionCacheOpenGL(RasterizerOpenGL& rasterizer)
|
||||
|
||||
@@ -30,12 +30,12 @@ public:
|
||||
explicit CachedGlobalRegion(VAddr addr, u32 size);
|
||||
|
||||
/// Gets the address of the shader in guest memory, required for cache management
|
||||
VAddr GetAddr() const {
|
||||
VAddr GetAddr() const override {
|
||||
return 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;
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public:
|
||||
private:
|
||||
GlobalRegion TryGetReservedGlobalRegion(VAddr addr, u32 size) const;
|
||||
GlobalRegion GetUncachedGlobalRegion(VAddr addr, u32 size);
|
||||
void ReserveGlobalRegion(const GlobalRegion& region);
|
||||
void ReserveGlobalRegion(GlobalRegion region);
|
||||
|
||||
std::unordered_map<VAddr, GlobalRegion> reserve;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
@@ -118,7 +119,7 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, Core::Syst
|
||||
|
||||
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &uniform_buffer_alignment);
|
||||
|
||||
LOG_CRITICAL(Render_OpenGL, "Sync fixed function OpenGL state here!");
|
||||
LOG_DEBUG(Render_OpenGL, "Sync fixed function OpenGL state here");
|
||||
CheckExtensions();
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
@@ -177,7 +178,7 @@ GLuint RasterizerOpenGL::SetupVertexFormat() {
|
||||
continue;
|
||||
|
||||
const auto& buffer = regs.vertex_array[attrib.buffer];
|
||||
LOG_TRACE(HW_GPU,
|
||||
LOG_TRACE(Render_OpenGL,
|
||||
"vertex attrib {}, count={}, size={}, type={}, offset={}, normalize={}",
|
||||
index, attrib.ComponentCount(), attrib.SizeString(), attrib.TypeString(),
|
||||
attrib.offset.Value(), attrib.IsNormalized());
|
||||
@@ -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{};
|
||||
@@ -343,9 +344,8 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
|
||||
shader_program_manager->UseProgrammableFragmentShader(program_handle);
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented shader index={}, enable={}, offset=0x{:08X}", index,
|
||||
shader_config.enable.Value(), shader_config.offset);
|
||||
UNREACHABLE();
|
||||
UNIMPLEMENTED_MSG("Unimplemented shader index={}, enable={}, offset=0x{:08X}", index,
|
||||
shader_config.enable.Value(), shader_config.offset);
|
||||
}
|
||||
|
||||
const auto stage_enum = static_cast<Maxwell::ShaderStage>(stage);
|
||||
@@ -414,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) {
|
||||
@@ -432,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());
|
||||
@@ -488,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,
|
||||
@@ -582,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{};
|
||||
@@ -673,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);
|
||||
@@ -793,7 +793,10 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config,
|
||||
VideoCore::Surface::PixelFormatFromGPUPixelFormat(config.pixel_format)};
|
||||
ASSERT_MSG(params.width == config.width, "Framebuffer width is different");
|
||||
ASSERT_MSG(params.height == config.height, "Framebuffer height is different");
|
||||
ASSERT_MSG(params.pixel_format == pixel_format, "Framebuffer pixel_format is different");
|
||||
|
||||
if (params.pixel_format != pixel_format) {
|
||||
LOG_WARNING(Render_OpenGL, "Framebuffer pixel_format is different");
|
||||
}
|
||||
|
||||
screen_info.display_texture = surface->Texture().handle;
|
||||
|
||||
@@ -802,104 +805,87 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config,
|
||||
|
||||
void RasterizerOpenGL::SamplerInfo::Create() {
|
||||
sampler.Create();
|
||||
mag_filter = min_filter = Tegra::Texture::TextureFilter::Linear;
|
||||
wrap_u = wrap_v = wrap_p = Tegra::Texture::WrapMode::Wrap;
|
||||
uses_depth_compare = false;
|
||||
mag_filter = Tegra::Texture::TextureFilter::Linear;
|
||||
min_filter = Tegra::Texture::TextureFilter::Linear;
|
||||
wrap_u = Tegra::Texture::WrapMode::Wrap;
|
||||
wrap_v = Tegra::Texture::WrapMode::Wrap;
|
||||
wrap_p = Tegra::Texture::WrapMode::Wrap;
|
||||
use_depth_compare = false;
|
||||
depth_compare_func = Tegra::Texture::DepthCompareFunc::Never;
|
||||
|
||||
// default is GL_LINEAR_MIPMAP_LINEAR
|
||||
// OpenGL's default is GL_LINEAR_MIPMAP_LINEAR
|
||||
glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
// Other attributes have correct defaults
|
||||
glSamplerParameteri(sampler.handle, GL_TEXTURE_COMPARE_FUNC, GL_NEVER);
|
||||
|
||||
// Other attributes have correct defaults
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntry& config) {
|
||||
const GLuint s = sampler.handle;
|
||||
const GLuint sampler_id = sampler.handle;
|
||||
if (mag_filter != config.mag_filter) {
|
||||
mag_filter = config.mag_filter;
|
||||
glSamplerParameteri(
|
||||
s, GL_TEXTURE_MAG_FILTER,
|
||||
sampler_id, GL_TEXTURE_MAG_FILTER,
|
||||
MaxwellToGL::TextureFilterMode(mag_filter, Tegra::Texture::TextureMipmapFilter::None));
|
||||
}
|
||||
if (min_filter != config.min_filter || mip_filter != config.mip_filter) {
|
||||
if (min_filter != config.min_filter || mipmap_filter != config.mipmap_filter) {
|
||||
min_filter = config.min_filter;
|
||||
mip_filter = config.mip_filter;
|
||||
glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER,
|
||||
MaxwellToGL::TextureFilterMode(min_filter, mip_filter));
|
||||
mipmap_filter = config.mipmap_filter;
|
||||
glSamplerParameteri(sampler_id, GL_TEXTURE_MIN_FILTER,
|
||||
MaxwellToGL::TextureFilterMode(min_filter, mipmap_filter));
|
||||
}
|
||||
|
||||
if (wrap_u != config.wrap_u) {
|
||||
wrap_u = config.wrap_u;
|
||||
glSamplerParameteri(s, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(wrap_u));
|
||||
glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(wrap_u));
|
||||
}
|
||||
if (wrap_v != config.wrap_v) {
|
||||
wrap_v = config.wrap_v;
|
||||
glSamplerParameteri(s, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(wrap_v));
|
||||
glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(wrap_v));
|
||||
}
|
||||
if (wrap_p != config.wrap_p) {
|
||||
wrap_p = config.wrap_p;
|
||||
glSamplerParameteri(s, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(wrap_p));
|
||||
glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(wrap_p));
|
||||
}
|
||||
|
||||
if (uses_depth_compare != (config.depth_compare_enabled == 1)) {
|
||||
uses_depth_compare = (config.depth_compare_enabled == 1);
|
||||
if (uses_depth_compare) {
|
||||
glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
|
||||
} else {
|
||||
glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, GL_NONE);
|
||||
}
|
||||
if (const bool enabled = config.depth_compare_enabled == 1; use_depth_compare != enabled) {
|
||||
use_depth_compare = enabled;
|
||||
glSamplerParameteri(sampler_id, GL_TEXTURE_COMPARE_MODE,
|
||||
use_depth_compare ? GL_COMPARE_REF_TO_TEXTURE : GL_NONE);
|
||||
}
|
||||
|
||||
if (depth_compare_func != config.depth_compare_func) {
|
||||
depth_compare_func = config.depth_compare_func;
|
||||
glSamplerParameteri(s, GL_TEXTURE_COMPARE_FUNC,
|
||||
glSamplerParameteri(sampler_id, GL_TEXTURE_COMPARE_FUNC,
|
||||
MaxwellToGL::DepthCompareFunc(depth_compare_func));
|
||||
}
|
||||
|
||||
GLvec4 new_border_color;
|
||||
if (config.srgb_conversion) {
|
||||
new_border_color[0] = config.srgb_border_color_r / 255.0f;
|
||||
new_border_color[1] = config.srgb_border_color_g / 255.0f;
|
||||
new_border_color[2] = config.srgb_border_color_g / 255.0f;
|
||||
} else {
|
||||
new_border_color[0] = config.border_color_r;
|
||||
new_border_color[1] = config.border_color_g;
|
||||
new_border_color[2] = config.border_color_b;
|
||||
}
|
||||
new_border_color[3] = config.border_color_a;
|
||||
|
||||
if (border_color != new_border_color) {
|
||||
if (const auto new_border_color = config.GetBorderColor(); border_color != new_border_color) {
|
||||
border_color = new_border_color;
|
||||
glSamplerParameterfv(s, GL_TEXTURE_BORDER_COLOR, border_color.data());
|
||||
glSamplerParameterfv(sampler_id, GL_TEXTURE_BORDER_COLOR, border_color.data());
|
||||
}
|
||||
|
||||
const float anisotropic_max = static_cast<float>(1 << config.max_anisotropy.Value());
|
||||
if (anisotropic_max != max_anisotropic) {
|
||||
max_anisotropic = anisotropic_max;
|
||||
if (const float anisotropic = config.GetMaxAnisotropy(); max_anisotropic != anisotropic) {
|
||||
max_anisotropic = anisotropic;
|
||||
if (GLAD_GL_ARB_texture_filter_anisotropic) {
|
||||
glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY, max_anisotropic);
|
||||
glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_ANISOTROPY, max_anisotropic);
|
||||
} else if (GLAD_GL_EXT_texture_filter_anisotropic) {
|
||||
glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropic);
|
||||
glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropic);
|
||||
}
|
||||
}
|
||||
const float lod_min = static_cast<float>(config.min_lod_clamp.Value()) / 256.0f;
|
||||
if (lod_min != min_lod) {
|
||||
min_lod = lod_min;
|
||||
glSamplerParameterf(s, GL_TEXTURE_MIN_LOD, min_lod);
|
||||
|
||||
if (const float min = config.GetMinLod(); min_lod != min) {
|
||||
min_lod = min;
|
||||
glSamplerParameterf(sampler_id, GL_TEXTURE_MIN_LOD, min_lod);
|
||||
}
|
||||
if (const float max = config.GetMaxLod(); max_lod != max) {
|
||||
max_lod = max;
|
||||
glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_LOD, max_lod);
|
||||
}
|
||||
|
||||
const float lod_max = static_cast<float>(config.max_lod_clamp.Value()) / 256.0f;
|
||||
if (lod_max != max_lod) {
|
||||
max_lod = lod_max;
|
||||
glSamplerParameterf(s, GL_TEXTURE_MAX_LOD, max_lod);
|
||||
}
|
||||
const u32 bias = config.mip_lod_bias.Value();
|
||||
// Sign extend the 13-bit value.
|
||||
constexpr u32 mask = 1U << (13 - 1);
|
||||
const float bias_lod = static_cast<s32>((bias ^ mask) - mask) / 256.f;
|
||||
if (lod_bias != bias_lod) {
|
||||
lod_bias = bias_lod;
|
||||
glSamplerParameterf(s, GL_TEXTURE_LOD_BIAS, lod_bias);
|
||||
if (const float bias = config.GetLodBias(); lod_bias != bias) {
|
||||
lod_bias = bias;
|
||||
glSamplerParameterf(sampler_id, GL_TEXTURE_LOD_BIAS, lod_bias);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -907,7 +893,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;
|
||||
@@ -939,8 +925,8 @@ void RasterizerOpenGL::SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::Shader
|
||||
size = buffer.size;
|
||||
|
||||
if (size > MaxConstbufferSize) {
|
||||
LOG_CRITICAL(HW_GPU, "indirect constbuffer size {} exceeds maximum {}", size,
|
||||
MaxConstbufferSize);
|
||||
LOG_WARNING(Render_OpenGL, "Indirect constbuffer size {} exceeds maximum {}", size,
|
||||
MaxConstbufferSize);
|
||||
size = MaxConstbufferSize;
|
||||
}
|
||||
} else {
|
||||
@@ -986,7 +972,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;
|
||||
|
||||
@@ -1000,10 +986,9 @@ void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& s
|
||||
|
||||
texture_samplers[current_bindpoint].SyncWithConfig(texture.tsc);
|
||||
|
||||
Surface surface = res_cache.GetTextureSurface(texture, entry);
|
||||
if (surface != nullptr) {
|
||||
if (Surface surface = res_cache.GetTextureSurface(texture, entry); surface) {
|
||||
state.texture_units[current_bindpoint].texture =
|
||||
entry.IsArray() ? surface->TextureLayer().handle : surface->Texture().handle;
|
||||
surface->Texture(entry.IsArray()).handle;
|
||||
surface->UpdateSwizzle(texture.tic.x_source, texture.tic.y_source, texture.tic.z_source,
|
||||
texture.tic.w_source);
|
||||
} else {
|
||||
@@ -1014,7 +999,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 =
|
||||
@@ -1037,7 +1022,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,
|
||||
@@ -1054,7 +1039,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;
|
||||
|
||||
@@ -1078,14 +1063,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;
|
||||
@@ -1097,7 +1082,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) {
|
||||
@@ -1131,7 +1116,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++) {
|
||||
@@ -1145,18 +1130,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;
|
||||
@@ -1198,7 +1183,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;
|
||||
|
||||
@@ -1212,7 +1197,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 =
|
||||
@@ -1234,21 +1219,17 @@ void RasterizerOpenGL::SyncScissorTest(OpenGLState& current_state) {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncTransformFeedback() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
|
||||
if (regs.tfb_enabled != 0) {
|
||||
LOG_CRITICAL(Render_OpenGL, "Transform feedbacks are not implemented");
|
||||
UNREACHABLE();
|
||||
}
|
||||
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;
|
||||
@@ -1258,13 +1239,9 @@ void RasterizerOpenGL::SyncPolygonOffset() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::CheckAlphaTests() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
|
||||
if (regs.alpha_test_enabled != 0 && regs.rt_control.count > 1) {
|
||||
LOG_CRITICAL(Render_OpenGL, "Alpha Testing is enabled with Multiple Render Targets, "
|
||||
"this behavior is undefined.");
|
||||
UNREACHABLE();
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
@@ -94,11 +94,12 @@ private:
|
||||
private:
|
||||
Tegra::Texture::TextureFilter mag_filter = Tegra::Texture::TextureFilter::Nearest;
|
||||
Tegra::Texture::TextureFilter min_filter = Tegra::Texture::TextureFilter::Nearest;
|
||||
Tegra::Texture::TextureMipmapFilter mip_filter = Tegra::Texture::TextureMipmapFilter::None;
|
||||
Tegra::Texture::TextureMipmapFilter mipmap_filter =
|
||||
Tegra::Texture::TextureMipmapFilter::None;
|
||||
Tegra::Texture::WrapMode wrap_u = Tegra::Texture::WrapMode::ClampToEdge;
|
||||
Tegra::Texture::WrapMode wrap_v = Tegra::Texture::WrapMode::ClampToEdge;
|
||||
Tegra::Texture::WrapMode wrap_p = Tegra::Texture::WrapMode::ClampToEdge;
|
||||
bool uses_depth_compare = false;
|
||||
bool use_depth_compare = false;
|
||||
Tegra::Texture::DepthCompareFunc depth_compare_func =
|
||||
Tegra::Texture::DepthCompareFunc::Always;
|
||||
GLvec4 border_color = {};
|
||||
@@ -214,6 +215,7 @@ private:
|
||||
GlobalRegionCacheOpenGL global_cache;
|
||||
|
||||
Core::Frontend::EmuWindow& emu_window;
|
||||
Core::System& system;
|
||||
|
||||
ScreenInfo& screen_info;
|
||||
|
||||
|
||||
@@ -400,6 +400,27 @@ static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType
|
||||
return format;
|
||||
}
|
||||
|
||||
/// Returns the discrepant array target
|
||||
constexpr GLenum GetArrayDiscrepantTarget(SurfaceTarget target) {
|
||||
switch (target) {
|
||||
case SurfaceTarget::Texture1D:
|
||||
return GL_TEXTURE_1D_ARRAY;
|
||||
case SurfaceTarget::Texture2D:
|
||||
return GL_TEXTURE_2D_ARRAY;
|
||||
case SurfaceTarget::Texture3D:
|
||||
return GL_NONE;
|
||||
case SurfaceTarget::Texture1DArray:
|
||||
return GL_TEXTURE_1D;
|
||||
case SurfaceTarget::Texture2DArray:
|
||||
return GL_TEXTURE_2D;
|
||||
case SurfaceTarget::TextureCubemap:
|
||||
return GL_TEXTURE_CUBE_MAP_ARRAY;
|
||||
case SurfaceTarget::TextureCubeArray:
|
||||
return GL_TEXTURE_CUBE_MAP;
|
||||
}
|
||||
return GL_NONE;
|
||||
}
|
||||
|
||||
Common::Rectangle<u32> SurfaceParams::GetRect(u32 mip_level) const {
|
||||
u32 actual_height{std::max(1U, unaligned_height >> mip_level)};
|
||||
if (IsPixelFormatASTC(pixel_format)) {
|
||||
@@ -425,7 +446,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.addr + offset);
|
||||
offset += layer_size;
|
||||
offset_gl += gl_size;
|
||||
}
|
||||
@@ -434,7 +455,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.addr + offset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -795,20 +816,22 @@ void CachedSurface::UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle,
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
}
|
||||
|
||||
void CachedSurface::EnsureTextureView() {
|
||||
if (texture_view.handle != 0)
|
||||
void CachedSurface::EnsureTextureDiscrepantView() {
|
||||
if (discrepant_view.handle != 0)
|
||||
return;
|
||||
|
||||
const GLenum target{TargetLayer()};
|
||||
const GLenum target{GetArrayDiscrepantTarget(params.target)};
|
||||
ASSERT(target != GL_NONE);
|
||||
|
||||
const GLuint num_layers{target == GL_TEXTURE_CUBE_MAP_ARRAY ? 6u : 1u};
|
||||
constexpr GLuint min_layer = 0;
|
||||
constexpr GLuint min_level = 0;
|
||||
|
||||
glGenTextures(1, &texture_view.handle);
|
||||
glTextureView(texture_view.handle, target, texture.handle, gl_internal_format, min_level,
|
||||
glGenTextures(1, &discrepant_view.handle);
|
||||
glTextureView(discrepant_view.handle, target, texture.handle, gl_internal_format, min_level,
|
||||
params.max_mip_level, min_layer, num_layers);
|
||||
ApplyTextureDefaults(texture_view.handle, params.max_mip_level);
|
||||
glTextureParameteriv(texture_view.handle, GL_TEXTURE_SWIZZLE_RGBA,
|
||||
ApplyTextureDefaults(discrepant_view.handle, params.max_mip_level);
|
||||
glTextureParameteriv(discrepant_view.handle, GL_TEXTURE_SWIZZLE_RGBA,
|
||||
reinterpret_cast<const GLint*>(swizzle.data()));
|
||||
}
|
||||
|
||||
@@ -834,8 +857,8 @@ void CachedSurface::UpdateSwizzle(Tegra::Texture::SwizzleSource swizzle_x,
|
||||
swizzle = {new_x, new_y, new_z, new_w};
|
||||
const auto swizzle_data = reinterpret_cast<const GLint*>(swizzle.data());
|
||||
glTextureParameteriv(texture.handle, GL_TEXTURE_SWIZZLE_RGBA, swizzle_data);
|
||||
if (texture_view.handle != 0) {
|
||||
glTextureParameteriv(texture_view.handle, GL_TEXTURE_SWIZZLE_RGBA, swizzle_data);
|
||||
if (discrepant_view.handle != 0) {
|
||||
glTextureParameteriv(discrepant_view.handle, GL_TEXTURE_SWIZZLE_RGBA, swizzle_data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -367,31 +367,19 @@ public:
|
||||
return texture;
|
||||
}
|
||||
|
||||
const OGLTexture& TextureLayer() {
|
||||
if (params.is_array) {
|
||||
return Texture();
|
||||
const OGLTexture& Texture(bool as_array) {
|
||||
if (params.is_array == as_array) {
|
||||
return texture;
|
||||
} else {
|
||||
EnsureTextureDiscrepantView();
|
||||
return discrepant_view;
|
||||
}
|
||||
EnsureTextureView();
|
||||
return texture_view;
|
||||
}
|
||||
|
||||
GLenum Target() const {
|
||||
return gl_target;
|
||||
}
|
||||
|
||||
GLenum TargetLayer() const {
|
||||
using VideoCore::Surface::SurfaceTarget;
|
||||
switch (params.target) {
|
||||
case SurfaceTarget::Texture1D:
|
||||
return GL_TEXTURE_1D_ARRAY;
|
||||
case SurfaceTarget::Texture2D:
|
||||
return GL_TEXTURE_2D_ARRAY;
|
||||
case SurfaceTarget::TextureCubemap:
|
||||
return GL_TEXTURE_CUBE_MAP_ARRAY;
|
||||
}
|
||||
return Target();
|
||||
}
|
||||
|
||||
const SurfaceParams& GetSurfaceParams() const {
|
||||
return params;
|
||||
}
|
||||
@@ -431,10 +419,10 @@ public:
|
||||
private:
|
||||
void UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle, GLuint draw_fb_handle);
|
||||
|
||||
void EnsureTextureView();
|
||||
void EnsureTextureDiscrepantView();
|
||||
|
||||
OGLTexture texture;
|
||||
OGLTexture texture_view;
|
||||
OGLTexture discrepant_view;
|
||||
std::vector<std::vector<u8>> gl_buffer;
|
||||
SurfaceParams params{};
|
||||
GLenum gl_target{};
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
@@ -717,7 +719,7 @@ private:
|
||||
}
|
||||
|
||||
std::string GenerateTexture(Operation operation, const std::string& func,
|
||||
bool is_extra_int = false) {
|
||||
const std::vector<std::pair<Type, Node>>& extras) {
|
||||
constexpr std::array<const char*, 4> coord_constructors = {"float", "vec2", "vec3", "vec4"};
|
||||
|
||||
const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
|
||||
@@ -738,36 +740,47 @@ private:
|
||||
expr += Visit(operation[i]);
|
||||
|
||||
const std::size_t next = i + 1;
|
||||
if (next < count || has_array || has_shadow)
|
||||
if (next < count)
|
||||
expr += ", ";
|
||||
}
|
||||
if (has_array) {
|
||||
expr += "float(ftoi(" + Visit(meta->array) + "))";
|
||||
expr += ", float(ftoi(" + Visit(meta->array) + "))";
|
||||
}
|
||||
if (has_shadow) {
|
||||
if (has_array)
|
||||
expr += ", ";
|
||||
expr += Visit(meta->depth_compare);
|
||||
expr += ", " + Visit(meta->depth_compare);
|
||||
}
|
||||
expr += ')';
|
||||
|
||||
for (const Node extra : meta->extras) {
|
||||
for (const auto& extra_pair : extras) {
|
||||
const auto [type, operand] = extra_pair;
|
||||
if (operand == nullptr) {
|
||||
continue;
|
||||
}
|
||||
expr += ", ";
|
||||
if (is_extra_int) {
|
||||
if (const auto immediate = std::get_if<ImmediateNode>(extra)) {
|
||||
|
||||
switch (type) {
|
||||
case Type::Int:
|
||||
if (const auto immediate = std::get_if<ImmediateNode>(operand)) {
|
||||
// Inline the string as an immediate integer in GLSL (some extra arguments are
|
||||
// required to be constant)
|
||||
expr += std::to_string(static_cast<s32>(immediate->GetValue()));
|
||||
} else {
|
||||
expr += "ftoi(" + Visit(extra) + ')';
|
||||
expr += "ftoi(" + Visit(operand) + ')';
|
||||
}
|
||||
} else {
|
||||
expr += Visit(extra);
|
||||
break;
|
||||
case Type::Float:
|
||||
expr += Visit(operand);
|
||||
break;
|
||||
default: {
|
||||
const auto type_int = static_cast<u32>(type);
|
||||
UNIMPLEMENTED_MSG("Unimplemented extra type={}", type_int);
|
||||
expr += '0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expr += ')';
|
||||
return expr;
|
||||
return expr + ')';
|
||||
}
|
||||
|
||||
std::string Assign(Operation operation) {
|
||||
@@ -1146,7 +1159,7 @@ private:
|
||||
const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
|
||||
ASSERT(meta);
|
||||
|
||||
std::string expr = GenerateTexture(operation, "texture");
|
||||
std::string expr = GenerateTexture(operation, "texture", {{Type::Float, meta->bias}});
|
||||
if (meta->sampler.IsShadow()) {
|
||||
expr = "vec4(" + expr + ')';
|
||||
}
|
||||
@@ -1157,7 +1170,7 @@ private:
|
||||
const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
|
||||
ASSERT(meta);
|
||||
|
||||
std::string expr = GenerateTexture(operation, "textureLod");
|
||||
std::string expr = GenerateTexture(operation, "textureLod", {{Type::Float, meta->lod}});
|
||||
if (meta->sampler.IsShadow()) {
|
||||
expr = "vec4(" + expr + ')';
|
||||
}
|
||||
@@ -1168,7 +1181,8 @@ private:
|
||||
const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
|
||||
ASSERT(meta);
|
||||
|
||||
return GenerateTexture(operation, "textureGather", !meta->sampler.IsShadow()) +
|
||||
const auto type = meta->sampler.IsShadow() ? Type::Float : Type::Int;
|
||||
return GenerateTexture(operation, "textureGather", {{type, meta->component}}) +
|
||||
GetSwizzle(meta->element);
|
||||
}
|
||||
|
||||
@@ -1197,8 +1211,8 @@ private:
|
||||
ASSERT(meta);
|
||||
|
||||
if (meta->element < 2) {
|
||||
return "itof(int((" + GenerateTexture(operation, "textureQueryLod") + " * vec2(256))" +
|
||||
GetSwizzle(meta->element) + "))";
|
||||
return "itof(int((" + GenerateTexture(operation, "textureQueryLod", {}) +
|
||||
" * vec2(256))" + GetSwizzle(meta->element) + "))";
|
||||
}
|
||||
return "0";
|
||||
}
|
||||
@@ -1224,9 +1238,9 @@ private:
|
||||
else if (next < count)
|
||||
expr += ", ";
|
||||
}
|
||||
for (std::size_t i = 0; i < meta->extras.size(); ++i) {
|
||||
if (meta->lod) {
|
||||
expr += ", ";
|
||||
expr += CastOperand(Visit(meta->extras.at(i)), Type::Int);
|
||||
expr += CastOperand(Visit(meta->lod), Type::Int);
|
||||
}
|
||||
expr += ')';
|
||||
|
||||
|
||||
@@ -167,9 +167,11 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf
|
||||
Memory::RasterizerFlushVirtualRegion(framebuffer_addr, size_in_bytes,
|
||||
Memory::FlushMode::Flush);
|
||||
|
||||
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));
|
||||
|
||||
|
||||
483
src/video_core/renderer_vulkan/maxwell_to_vk.cpp
Normal file
483
src/video_core/renderer_vulkan/maxwell_to_vk.cpp
Normal file
@@ -0,0 +1,483 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/renderer_vulkan/declarations.h"
|
||||
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
|
||||
#include "video_core/renderer_vulkan/vk_device.h"
|
||||
#include "video_core/surface.h"
|
||||
|
||||
namespace Vulkan::MaxwellToVK {
|
||||
|
||||
namespace Sampler {
|
||||
|
||||
vk::Filter Filter(Tegra::Texture::TextureFilter filter) {
|
||||
switch (filter) {
|
||||
case Tegra::Texture::TextureFilter::Linear:
|
||||
return vk::Filter::eLinear;
|
||||
case Tegra::Texture::TextureFilter::Nearest:
|
||||
return vk::Filter::eNearest;
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unimplemented sampler filter={}", static_cast<u32>(filter));
|
||||
return {};
|
||||
}
|
||||
|
||||
vk::SamplerMipmapMode MipmapMode(Tegra::Texture::TextureMipmapFilter mipmap_filter) {
|
||||
switch (mipmap_filter) {
|
||||
case Tegra::Texture::TextureMipmapFilter::None:
|
||||
// TODO(Rodrigo): None seems to be mapped to OpenGL's mag and min filters without mipmapping
|
||||
// (e.g. GL_NEAREST and GL_LINEAR). Vulkan doesn't have such a thing, find out if we have to
|
||||
// use an image view with a single mipmap level to emulate this.
|
||||
return vk::SamplerMipmapMode::eLinear;
|
||||
case Tegra::Texture::TextureMipmapFilter::Linear:
|
||||
return vk::SamplerMipmapMode::eLinear;
|
||||
case Tegra::Texture::TextureMipmapFilter::Nearest:
|
||||
return vk::SamplerMipmapMode::eNearest;
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unimplemented sampler mipmap mode={}", static_cast<u32>(mipmap_filter));
|
||||
return {};
|
||||
}
|
||||
|
||||
vk::SamplerAddressMode WrapMode(Tegra::Texture::WrapMode wrap_mode) {
|
||||
switch (wrap_mode) {
|
||||
case Tegra::Texture::WrapMode::Wrap:
|
||||
return vk::SamplerAddressMode::eRepeat;
|
||||
case Tegra::Texture::WrapMode::Mirror:
|
||||
return vk::SamplerAddressMode::eMirroredRepeat;
|
||||
case Tegra::Texture::WrapMode::ClampToEdge:
|
||||
return vk::SamplerAddressMode::eClampToEdge;
|
||||
case Tegra::Texture::WrapMode::Border:
|
||||
return vk::SamplerAddressMode::eClampToBorder;
|
||||
case Tegra::Texture::WrapMode::ClampOGL:
|
||||
// TODO(Rodrigo): GL_CLAMP was removed as of OpenGL 3.1, to implement GL_CLAMP, we can use
|
||||
// eClampToBorder to get the border color of the texture, and then sample the edge to
|
||||
// manually mix them. However the shader part of this is not yet implemented.
|
||||
return vk::SamplerAddressMode::eClampToBorder;
|
||||
case Tegra::Texture::WrapMode::MirrorOnceClampToEdge:
|
||||
return vk::SamplerAddressMode::eMirrorClampToEdge;
|
||||
case Tegra::Texture::WrapMode::MirrorOnceBorder:
|
||||
UNIMPLEMENTED();
|
||||
return vk::SamplerAddressMode::eMirrorClampToEdge;
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unimplemented wrap mode={}", static_cast<u32>(wrap_mode));
|
||||
return {};
|
||||
}
|
||||
|
||||
vk::CompareOp DepthCompareFunction(Tegra::Texture::DepthCompareFunc depth_compare_func) {
|
||||
switch (depth_compare_func) {
|
||||
case Tegra::Texture::DepthCompareFunc::Never:
|
||||
return vk::CompareOp::eNever;
|
||||
case Tegra::Texture::DepthCompareFunc::Less:
|
||||
return vk::CompareOp::eLess;
|
||||
case Tegra::Texture::DepthCompareFunc::LessEqual:
|
||||
return vk::CompareOp::eLessOrEqual;
|
||||
case Tegra::Texture::DepthCompareFunc::Equal:
|
||||
return vk::CompareOp::eEqual;
|
||||
case Tegra::Texture::DepthCompareFunc::NotEqual:
|
||||
return vk::CompareOp::eNotEqual;
|
||||
case Tegra::Texture::DepthCompareFunc::Greater:
|
||||
return vk::CompareOp::eGreater;
|
||||
case Tegra::Texture::DepthCompareFunc::GreaterEqual:
|
||||
return vk::CompareOp::eGreaterOrEqual;
|
||||
case Tegra::Texture::DepthCompareFunc::Always:
|
||||
return vk::CompareOp::eAlways;
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unimplemented sampler depth compare function={}",
|
||||
static_cast<u32>(depth_compare_func));
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace Sampler
|
||||
|
||||
struct FormatTuple {
|
||||
vk::Format format; ///< Vulkan format
|
||||
ComponentType component_type; ///< Abstracted component type
|
||||
bool attachable; ///< True when this format can be used as an attachment
|
||||
};
|
||||
|
||||
static constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex_format_tuples = {{
|
||||
{vk::Format::eA8B8G8R8UnormPack32, ComponentType::UNorm, true}, // ABGR8U
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // ABGR8S
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // ABGR8UI
|
||||
{vk::Format::eB5G6R5UnormPack16, ComponentType::UNorm, false}, // B5G6R5U
|
||||
{vk::Format::eA2B10G10R10UnormPack32, ComponentType::UNorm, true}, // A2B10G10R10U
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // A1B5G5R5U
|
||||
{vk::Format::eR8Unorm, ComponentType::UNorm, true}, // R8U
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // R8UI
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // RGBA16F
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // RGBA16U
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // RGBA16UI
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // R11FG11FB10F
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // RGBA32UI
|
||||
{vk::Format::eBc1RgbaUnormBlock, ComponentType::UNorm, false}, // DXT1
|
||||
{vk::Format::eBc2UnormBlock, ComponentType::UNorm, false}, // DXT23
|
||||
{vk::Format::eBc3UnormBlock, ComponentType::UNorm, false}, // DXT45
|
||||
{vk::Format::eBc4UnormBlock, ComponentType::UNorm, false}, // DXN1
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // DXN2UNORM
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // DXN2SNORM
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // BC7U
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // BC6H_UF16
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // BC6H_SF16
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_4X4
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // BGRA8
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // RGBA32F
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // RG32F
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // R32F
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // R16F
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // R16U
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // R16S
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // R16UI
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // R16I
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // RG16
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // RG16F
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // RG16UI
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // RG16I
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // RG16S
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // RGB32F
|
||||
{vk::Format::eA8B8G8R8SrgbPack32, ComponentType::UNorm, true}, // RGBA8_SRGB
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // RG8U
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // RG8S
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // RG32UI
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // R32UI
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_8X8
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_8X5
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_5X4
|
||||
|
||||
// Compressed sRGB formats
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // BGRA8_SRGB
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // DXT1_SRGB
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // DXT23_SRGB
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // DXT45_SRGB
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // BC7U_SRGB
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_4X4_SRGB
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_8X8_SRGB
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_8X5_SRGB
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_5X4_SRGB
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_5X5
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_5X5_SRGB
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_10X8
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_10X8_SRGB
|
||||
|
||||
// Depth formats
|
||||
{vk::Format::eD32Sfloat, ComponentType::Float, true}, // Z32F
|
||||
{vk::Format::eD16Unorm, ComponentType::UNorm, true}, // Z16
|
||||
|
||||
// DepthStencil formats
|
||||
{vk::Format::eD24UnormS8Uint, ComponentType::UNorm, true}, // Z24S8
|
||||
{vk::Format::eD24UnormS8Uint, ComponentType::UNorm, true}, // S8Z24 (emulated)
|
||||
{vk::Format::eUndefined, ComponentType::Invalid, false}, // Z32FS8
|
||||
}};
|
||||
|
||||
static constexpr bool IsZetaFormat(PixelFormat pixel_format) {
|
||||
return pixel_format >= PixelFormat::MaxColorFormat &&
|
||||
pixel_format < PixelFormat::MaxDepthStencilFormat;
|
||||
}
|
||||
|
||||
std::pair<vk::Format, bool> SurfaceFormat(const VKDevice& device, FormatType format_type,
|
||||
PixelFormat pixel_format, ComponentType component_type) {
|
||||
ASSERT(static_cast<std::size_t>(pixel_format) < tex_format_tuples.size());
|
||||
|
||||
const auto tuple = tex_format_tuples[static_cast<u32>(pixel_format)];
|
||||
UNIMPLEMENTED_IF_MSG(tuple.format == vk::Format::eUndefined,
|
||||
"Unimplemented texture format with pixel format={} and component type={}",
|
||||
static_cast<u32>(pixel_format), static_cast<u32>(component_type));
|
||||
ASSERT_MSG(component_type == tuple.component_type, "Component type mismatch");
|
||||
|
||||
auto usage = vk::FormatFeatureFlagBits::eSampledImage |
|
||||
vk::FormatFeatureFlagBits::eTransferDst | vk::FormatFeatureFlagBits::eTransferSrc;
|
||||
if (tuple.attachable) {
|
||||
usage |= IsZetaFormat(pixel_format) ? vk::FormatFeatureFlagBits::eDepthStencilAttachment
|
||||
: vk::FormatFeatureFlagBits::eColorAttachment;
|
||||
}
|
||||
return {device.GetSupportedFormat(tuple.format, usage, format_type), tuple.attachable};
|
||||
}
|
||||
|
||||
vk::ShaderStageFlagBits ShaderStage(Maxwell::ShaderStage stage) {
|
||||
switch (stage) {
|
||||
case Maxwell::ShaderStage::Vertex:
|
||||
return vk::ShaderStageFlagBits::eVertex;
|
||||
case Maxwell::ShaderStage::TesselationControl:
|
||||
return vk::ShaderStageFlagBits::eTessellationControl;
|
||||
case Maxwell::ShaderStage::TesselationEval:
|
||||
return vk::ShaderStageFlagBits::eTessellationEvaluation;
|
||||
case Maxwell::ShaderStage::Geometry:
|
||||
return vk::ShaderStageFlagBits::eGeometry;
|
||||
case Maxwell::ShaderStage::Fragment:
|
||||
return vk::ShaderStageFlagBits::eFragment;
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unimplemented shader stage={}", static_cast<u32>(stage));
|
||||
return {};
|
||||
}
|
||||
|
||||
vk::PrimitiveTopology PrimitiveTopology(Maxwell::PrimitiveTopology topology) {
|
||||
switch (topology) {
|
||||
case Maxwell::PrimitiveTopology::Points:
|
||||
return vk::PrimitiveTopology::ePointList;
|
||||
case Maxwell::PrimitiveTopology::Lines:
|
||||
return vk::PrimitiveTopology::eLineList;
|
||||
case Maxwell::PrimitiveTopology::LineStrip:
|
||||
return vk::PrimitiveTopology::eLineStrip;
|
||||
case Maxwell::PrimitiveTopology::Triangles:
|
||||
return vk::PrimitiveTopology::eTriangleList;
|
||||
case Maxwell::PrimitiveTopology::TriangleStrip:
|
||||
return vk::PrimitiveTopology::eTriangleStrip;
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unimplemented topology={}", static_cast<u32>(topology));
|
||||
return {};
|
||||
}
|
||||
|
||||
vk::Format VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttribute::Size size) {
|
||||
switch (type) {
|
||||
case Maxwell::VertexAttribute::Type::SignedNorm:
|
||||
break;
|
||||
case Maxwell::VertexAttribute::Type::UnsignedNorm:
|
||||
switch (size) {
|
||||
case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
|
||||
return vk::Format::eR8G8B8A8Unorm;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Maxwell::VertexAttribute::Type::SignedInt:
|
||||
break;
|
||||
case Maxwell::VertexAttribute::Type::UnsignedInt:
|
||||
switch (size) {
|
||||
case Maxwell::VertexAttribute::Size::Size_32:
|
||||
return vk::Format::eR32Uint;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
case Maxwell::VertexAttribute::Type::UnsignedScaled:
|
||||
case Maxwell::VertexAttribute::Type::SignedScaled:
|
||||
break;
|
||||
case Maxwell::VertexAttribute::Type::Float:
|
||||
switch (size) {
|
||||
case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
|
||||
return vk::Format::eR32G32B32A32Sfloat;
|
||||
case Maxwell::VertexAttribute::Size::Size_32_32_32:
|
||||
return vk::Format::eR32G32B32Sfloat;
|
||||
case Maxwell::VertexAttribute::Size::Size_32_32:
|
||||
return vk::Format::eR32G32Sfloat;
|
||||
case Maxwell::VertexAttribute::Size::Size_32:
|
||||
return vk::Format::eR32Sfloat;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unimplemented vertex format of type={} and size={}", static_cast<u32>(type),
|
||||
static_cast<u32>(size));
|
||||
return {};
|
||||
}
|
||||
|
||||
vk::CompareOp ComparisonOp(Maxwell::ComparisonOp comparison) {
|
||||
switch (comparison) {
|
||||
case Maxwell::ComparisonOp::Never:
|
||||
case Maxwell::ComparisonOp::NeverOld:
|
||||
return vk::CompareOp::eNever;
|
||||
case Maxwell::ComparisonOp::Less:
|
||||
case Maxwell::ComparisonOp::LessOld:
|
||||
return vk::CompareOp::eLess;
|
||||
case Maxwell::ComparisonOp::Equal:
|
||||
case Maxwell::ComparisonOp::EqualOld:
|
||||
return vk::CompareOp::eEqual;
|
||||
case Maxwell::ComparisonOp::LessEqual:
|
||||
case Maxwell::ComparisonOp::LessEqualOld:
|
||||
return vk::CompareOp::eLessOrEqual;
|
||||
case Maxwell::ComparisonOp::Greater:
|
||||
case Maxwell::ComparisonOp::GreaterOld:
|
||||
return vk::CompareOp::eGreater;
|
||||
case Maxwell::ComparisonOp::NotEqual:
|
||||
case Maxwell::ComparisonOp::NotEqualOld:
|
||||
return vk::CompareOp::eNotEqual;
|
||||
case Maxwell::ComparisonOp::GreaterEqual:
|
||||
case Maxwell::ComparisonOp::GreaterEqualOld:
|
||||
return vk::CompareOp::eGreaterOrEqual;
|
||||
case Maxwell::ComparisonOp::Always:
|
||||
case Maxwell::ComparisonOp::AlwaysOld:
|
||||
return vk::CompareOp::eAlways;
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unimplemented comparison op={}", static_cast<u32>(comparison));
|
||||
return {};
|
||||
}
|
||||
|
||||
vk::IndexType IndexFormat(Maxwell::IndexFormat index_format) {
|
||||
switch (index_format) {
|
||||
case Maxwell::IndexFormat::UnsignedByte:
|
||||
UNIMPLEMENTED_MSG("Vulkan does not support native u8 index format");
|
||||
return vk::IndexType::eUint16;
|
||||
case Maxwell::IndexFormat::UnsignedShort:
|
||||
return vk::IndexType::eUint16;
|
||||
case Maxwell::IndexFormat::UnsignedInt:
|
||||
return vk::IndexType::eUint32;
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unimplemented index_format={}", static_cast<u32>(index_format));
|
||||
return {};
|
||||
}
|
||||
|
||||
vk::StencilOp StencilOp(Maxwell::StencilOp stencil_op) {
|
||||
switch (stencil_op) {
|
||||
case Maxwell::StencilOp::Keep:
|
||||
case Maxwell::StencilOp::KeepOGL:
|
||||
return vk::StencilOp::eKeep;
|
||||
case Maxwell::StencilOp::Zero:
|
||||
case Maxwell::StencilOp::ZeroOGL:
|
||||
return vk::StencilOp::eZero;
|
||||
case Maxwell::StencilOp::Replace:
|
||||
case Maxwell::StencilOp::ReplaceOGL:
|
||||
return vk::StencilOp::eReplace;
|
||||
case Maxwell::StencilOp::Incr:
|
||||
case Maxwell::StencilOp::IncrOGL:
|
||||
return vk::StencilOp::eIncrementAndClamp;
|
||||
case Maxwell::StencilOp::Decr:
|
||||
case Maxwell::StencilOp::DecrOGL:
|
||||
return vk::StencilOp::eDecrementAndClamp;
|
||||
case Maxwell::StencilOp::Invert:
|
||||
case Maxwell::StencilOp::InvertOGL:
|
||||
return vk::StencilOp::eInvert;
|
||||
case Maxwell::StencilOp::IncrWrap:
|
||||
case Maxwell::StencilOp::IncrWrapOGL:
|
||||
return vk::StencilOp::eIncrementAndWrap;
|
||||
case Maxwell::StencilOp::DecrWrap:
|
||||
case Maxwell::StencilOp::DecrWrapOGL:
|
||||
return vk::StencilOp::eDecrementAndWrap;
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unimplemented stencil op={}", static_cast<u32>(stencil_op));
|
||||
return {};
|
||||
}
|
||||
|
||||
vk::BlendOp BlendEquation(Maxwell::Blend::Equation equation) {
|
||||
switch (equation) {
|
||||
case Maxwell::Blend::Equation::Add:
|
||||
case Maxwell::Blend::Equation::AddGL:
|
||||
return vk::BlendOp::eAdd;
|
||||
case Maxwell::Blend::Equation::Subtract:
|
||||
case Maxwell::Blend::Equation::SubtractGL:
|
||||
return vk::BlendOp::eSubtract;
|
||||
case Maxwell::Blend::Equation::ReverseSubtract:
|
||||
case Maxwell::Blend::Equation::ReverseSubtractGL:
|
||||
return vk::BlendOp::eReverseSubtract;
|
||||
case Maxwell::Blend::Equation::Min:
|
||||
case Maxwell::Blend::Equation::MinGL:
|
||||
return vk::BlendOp::eMin;
|
||||
case Maxwell::Blend::Equation::Max:
|
||||
case Maxwell::Blend::Equation::MaxGL:
|
||||
return vk::BlendOp::eMax;
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unimplemented blend equation={}", static_cast<u32>(equation));
|
||||
return {};
|
||||
}
|
||||
|
||||
vk::BlendFactor BlendFactor(Maxwell::Blend::Factor factor) {
|
||||
switch (factor) {
|
||||
case Maxwell::Blend::Factor::Zero:
|
||||
case Maxwell::Blend::Factor::ZeroGL:
|
||||
return vk::BlendFactor::eZero;
|
||||
case Maxwell::Blend::Factor::One:
|
||||
case Maxwell::Blend::Factor::OneGL:
|
||||
return vk::BlendFactor::eOne;
|
||||
case Maxwell::Blend::Factor::SourceColor:
|
||||
case Maxwell::Blend::Factor::SourceColorGL:
|
||||
return vk::BlendFactor::eSrcColor;
|
||||
case Maxwell::Blend::Factor::OneMinusSourceColor:
|
||||
case Maxwell::Blend::Factor::OneMinusSourceColorGL:
|
||||
return vk::BlendFactor::eOneMinusSrcColor;
|
||||
case Maxwell::Blend::Factor::SourceAlpha:
|
||||
case Maxwell::Blend::Factor::SourceAlphaGL:
|
||||
return vk::BlendFactor::eSrcAlpha;
|
||||
case Maxwell::Blend::Factor::OneMinusSourceAlpha:
|
||||
case Maxwell::Blend::Factor::OneMinusSourceAlphaGL:
|
||||
return vk::BlendFactor::eOneMinusSrcAlpha;
|
||||
case Maxwell::Blend::Factor::DestAlpha:
|
||||
case Maxwell::Blend::Factor::DestAlphaGL:
|
||||
return vk::BlendFactor::eDstAlpha;
|
||||
case Maxwell::Blend::Factor::OneMinusDestAlpha:
|
||||
case Maxwell::Blend::Factor::OneMinusDestAlphaGL:
|
||||
return vk::BlendFactor::eOneMinusDstAlpha;
|
||||
case Maxwell::Blend::Factor::DestColor:
|
||||
case Maxwell::Blend::Factor::DestColorGL:
|
||||
return vk::BlendFactor::eDstColor;
|
||||
case Maxwell::Blend::Factor::OneMinusDestColor:
|
||||
case Maxwell::Blend::Factor::OneMinusDestColorGL:
|
||||
return vk::BlendFactor::eOneMinusDstColor;
|
||||
case Maxwell::Blend::Factor::SourceAlphaSaturate:
|
||||
case Maxwell::Blend::Factor::SourceAlphaSaturateGL:
|
||||
return vk::BlendFactor::eSrcAlphaSaturate;
|
||||
case Maxwell::Blend::Factor::Source1Color:
|
||||
case Maxwell::Blend::Factor::Source1ColorGL:
|
||||
return vk::BlendFactor::eSrc1Color;
|
||||
case Maxwell::Blend::Factor::OneMinusSource1Color:
|
||||
case Maxwell::Blend::Factor::OneMinusSource1ColorGL:
|
||||
return vk::BlendFactor::eOneMinusSrc1Color;
|
||||
case Maxwell::Blend::Factor::Source1Alpha:
|
||||
case Maxwell::Blend::Factor::Source1AlphaGL:
|
||||
return vk::BlendFactor::eSrc1Alpha;
|
||||
case Maxwell::Blend::Factor::OneMinusSource1Alpha:
|
||||
case Maxwell::Blend::Factor::OneMinusSource1AlphaGL:
|
||||
return vk::BlendFactor::eOneMinusSrc1Alpha;
|
||||
case Maxwell::Blend::Factor::ConstantColor:
|
||||
case Maxwell::Blend::Factor::ConstantColorGL:
|
||||
return vk::BlendFactor::eConstantColor;
|
||||
case Maxwell::Blend::Factor::OneMinusConstantColor:
|
||||
case Maxwell::Blend::Factor::OneMinusConstantColorGL:
|
||||
return vk::BlendFactor::eOneMinusConstantColor;
|
||||
case Maxwell::Blend::Factor::ConstantAlpha:
|
||||
case Maxwell::Blend::Factor::ConstantAlphaGL:
|
||||
return vk::BlendFactor::eConstantAlpha;
|
||||
case Maxwell::Blend::Factor::OneMinusConstantAlpha:
|
||||
case Maxwell::Blend::Factor::OneMinusConstantAlphaGL:
|
||||
return vk::BlendFactor::eOneMinusConstantAlpha;
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unimplemented blend factor={}", static_cast<u32>(factor));
|
||||
return {};
|
||||
}
|
||||
|
||||
vk::FrontFace FrontFace(Maxwell::Cull::FrontFace front_face) {
|
||||
switch (front_face) {
|
||||
case Maxwell::Cull::FrontFace::ClockWise:
|
||||
return vk::FrontFace::eClockwise;
|
||||
case Maxwell::Cull::FrontFace::CounterClockWise:
|
||||
return vk::FrontFace::eCounterClockwise;
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unimplemented front face={}", static_cast<u32>(front_face));
|
||||
return {};
|
||||
}
|
||||
|
||||
vk::CullModeFlags CullFace(Maxwell::Cull::CullFace cull_face) {
|
||||
switch (cull_face) {
|
||||
case Maxwell::Cull::CullFace::Front:
|
||||
return vk::CullModeFlagBits::eFront;
|
||||
case Maxwell::Cull::CullFace::Back:
|
||||
return vk::CullModeFlagBits::eBack;
|
||||
case Maxwell::Cull::CullFace::FrontAndBack:
|
||||
return vk::CullModeFlagBits::eFrontAndBack;
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unimplemented cull face={}", static_cast<u32>(cull_face));
|
||||
return {};
|
||||
}
|
||||
|
||||
vk::ComponentSwizzle SwizzleSource(Tegra::Texture::SwizzleSource swizzle) {
|
||||
switch (swizzle) {
|
||||
case Tegra::Texture::SwizzleSource::Zero:
|
||||
return vk::ComponentSwizzle::eZero;
|
||||
case Tegra::Texture::SwizzleSource::R:
|
||||
return vk::ComponentSwizzle::eR;
|
||||
case Tegra::Texture::SwizzleSource::G:
|
||||
return vk::ComponentSwizzle::eG;
|
||||
case Tegra::Texture::SwizzleSource::B:
|
||||
return vk::ComponentSwizzle::eB;
|
||||
case Tegra::Texture::SwizzleSource::A:
|
||||
return vk::ComponentSwizzle::eA;
|
||||
case Tegra::Texture::SwizzleSource::OneInt:
|
||||
case Tegra::Texture::SwizzleSource::OneFloat:
|
||||
return vk::ComponentSwizzle::eOne;
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unimplemented swizzle source={}", static_cast<u32>(swizzle));
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace Vulkan::MaxwellToVK
|
||||
58
src/video_core/renderer_vulkan/maxwell_to_vk.h
Normal file
58
src/video_core/renderer_vulkan/maxwell_to_vk.h
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/renderer_vulkan/declarations.h"
|
||||
#include "video_core/renderer_vulkan/vk_device.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
|
||||
namespace Vulkan::MaxwellToVK {
|
||||
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
using PixelFormat = VideoCore::Surface::PixelFormat;
|
||||
using ComponentType = VideoCore::Surface::ComponentType;
|
||||
|
||||
namespace Sampler {
|
||||
|
||||
vk::Filter Filter(Tegra::Texture::TextureFilter filter);
|
||||
|
||||
vk::SamplerMipmapMode MipmapMode(Tegra::Texture::TextureMipmapFilter mipmap_filter);
|
||||
|
||||
vk::SamplerAddressMode WrapMode(Tegra::Texture::WrapMode wrap_mode);
|
||||
|
||||
vk::CompareOp DepthCompareFunction(Tegra::Texture::DepthCompareFunc depth_compare_func);
|
||||
|
||||
} // namespace Sampler
|
||||
|
||||
std::pair<vk::Format, bool> SurfaceFormat(const VKDevice& device, FormatType format_type,
|
||||
PixelFormat pixel_format, ComponentType component_type);
|
||||
|
||||
vk::ShaderStageFlagBits ShaderStage(Maxwell::ShaderStage stage);
|
||||
|
||||
vk::PrimitiveTopology PrimitiveTopology(Maxwell::PrimitiveTopology topology);
|
||||
|
||||
vk::Format VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttribute::Size size);
|
||||
|
||||
vk::CompareOp ComparisonOp(Maxwell::ComparisonOp comparison);
|
||||
|
||||
vk::IndexType IndexFormat(Maxwell::IndexFormat index_format);
|
||||
|
||||
vk::StencilOp StencilOp(Maxwell::StencilOp stencil_op);
|
||||
|
||||
vk::BlendOp BlendEquation(Maxwell::Blend::Equation equation);
|
||||
|
||||
vk::BlendFactor BlendFactor(Maxwell::Blend::Factor factor);
|
||||
|
||||
vk::FrontFace FrontFace(Maxwell::Cull::FrontFace front_face);
|
||||
|
||||
vk::CullModeFlags CullFace(Maxwell::Cull::CullFace cull_face);
|
||||
|
||||
vk::ComponentSwizzle SwizzleSource(Tegra::Texture::SwizzleSource swizzle);
|
||||
|
||||
} // namespace Vulkan::MaxwellToVK
|
||||
@@ -122,8 +122,7 @@ bool VKDevice::IsFormatSupported(vk::Format wanted_format, vk::FormatFeatureFlag
|
||||
FormatType format_type) const {
|
||||
const auto it = format_properties.find(wanted_format);
|
||||
if (it == format_properties.end()) {
|
||||
LOG_CRITICAL(Render_Vulkan, "Unimplemented format query={}",
|
||||
static_cast<u32>(wanted_format));
|
||||
LOG_CRITICAL(Render_Vulkan, "Unimplemented format query={}", vk::to_string(wanted_format));
|
||||
UNREACHABLE();
|
||||
return true;
|
||||
}
|
||||
@@ -219,11 +218,19 @@ std::map<vk::Format, vk::FormatProperties> VKDevice::GetFormatProperties(
|
||||
format_properties.emplace(format, physical.getFormatProperties(format, dldi));
|
||||
};
|
||||
AddFormatQuery(vk::Format::eA8B8G8R8UnormPack32);
|
||||
AddFormatQuery(vk::Format::eR5G6B5UnormPack16);
|
||||
AddFormatQuery(vk::Format::eB5G6R5UnormPack16);
|
||||
AddFormatQuery(vk::Format::eA2B10G10R10UnormPack32);
|
||||
AddFormatQuery(vk::Format::eR8G8B8A8Srgb);
|
||||
AddFormatQuery(vk::Format::eR8Unorm);
|
||||
AddFormatQuery(vk::Format::eD32Sfloat);
|
||||
AddFormatQuery(vk::Format::eD16Unorm);
|
||||
AddFormatQuery(vk::Format::eD16UnormS8Uint);
|
||||
AddFormatQuery(vk::Format::eD24UnormS8Uint);
|
||||
AddFormatQuery(vk::Format::eD32SfloatS8Uint);
|
||||
AddFormatQuery(vk::Format::eBc1RgbaUnormBlock);
|
||||
AddFormatQuery(vk::Format::eBc2UnormBlock);
|
||||
AddFormatQuery(vk::Format::eBc3UnormBlock);
|
||||
AddFormatQuery(vk::Format::eBc4UnormBlock);
|
||||
|
||||
return format_properties;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -165,6 +165,7 @@ u32 ShaderIR::DecodeInstr(NodeBlock& bb, u32 pc) {
|
||||
{OpCode::Type::Hfma2, &ShaderIR::DecodeHfma2},
|
||||
{OpCode::Type::Conversion, &ShaderIR::DecodeConversion},
|
||||
{OpCode::Type::Memory, &ShaderIR::DecodeMemory},
|
||||
{OpCode::Type::Texture, &ShaderIR::DecodeTexture},
|
||||
{OpCode::Type::FloatSetPredicate, &ShaderIR::DecodeFloatSetPredicate},
|
||||
{OpCode::Type::IntegerSetPredicate, &ShaderIR::DecodeIntegerSetPredicate},
|
||||
{OpCode::Type::HalfSetPredicate, &ShaderIR::DecodeHalfSetPredicate},
|
||||
|
||||
@@ -17,24 +17,6 @@ using Tegra::Shader::Attribute;
|
||||
using Tegra::Shader::Instruction;
|
||||
using Tegra::Shader::OpCode;
|
||||
using Tegra::Shader::Register;
|
||||
using Tegra::Shader::TextureMiscMode;
|
||||
using Tegra::Shader::TextureProcessMode;
|
||||
using Tegra::Shader::TextureType;
|
||||
|
||||
static std::size_t GetCoordCount(TextureType texture_type) {
|
||||
switch (texture_type) {
|
||||
case TextureType::Texture1D:
|
||||
return 1;
|
||||
case TextureType::Texture2D:
|
||||
return 2;
|
||||
case TextureType::Texture3D:
|
||||
case TextureType::TextureCube:
|
||||
return 3;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unhandled texture type: {}", static_cast<u32>(texture_type));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
|
||||
const Instruction instr = {program_code[pc]};
|
||||
@@ -247,194 +229,6 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TEX: {
|
||||
UNIMPLEMENTED_IF_MSG(instr.tex.UsesMiscMode(TextureMiscMode::AOFFI),
|
||||
"AOFFI is not implemented");
|
||||
|
||||
if (instr.tex.UsesMiscMode(TextureMiscMode::NODEP)) {
|
||||
LOG_WARNING(HW_GPU, "TEX.NODEP implementation is incomplete");
|
||||
}
|
||||
|
||||
const TextureType texture_type{instr.tex.texture_type};
|
||||
const bool is_array = instr.tex.array != 0;
|
||||
const bool depth_compare = instr.tex.UsesMiscMode(TextureMiscMode::DC);
|
||||
const auto process_mode = instr.tex.GetTextureProcessMode();
|
||||
WriteTexInstructionFloat(
|
||||
bb, instr, GetTexCode(instr, texture_type, process_mode, depth_compare, is_array));
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TEXS: {
|
||||
const TextureType texture_type{instr.texs.GetTextureType()};
|
||||
const bool is_array{instr.texs.IsArrayTexture()};
|
||||
const bool depth_compare = instr.texs.UsesMiscMode(TextureMiscMode::DC);
|
||||
const auto process_mode = instr.texs.GetTextureProcessMode();
|
||||
|
||||
if (instr.texs.UsesMiscMode(TextureMiscMode::NODEP)) {
|
||||
LOG_WARNING(HW_GPU, "TEXS.NODEP implementation is incomplete");
|
||||
}
|
||||
|
||||
const Node4 components =
|
||||
GetTexsCode(instr, texture_type, process_mode, depth_compare, is_array);
|
||||
|
||||
if (instr.texs.fp32_flag) {
|
||||
WriteTexsInstructionFloat(bb, instr, components);
|
||||
} else {
|
||||
WriteTexsInstructionHalfFloat(bb, instr, components);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TLD4: {
|
||||
ASSERT(instr.tld4.array == 0);
|
||||
UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::AOFFI),
|
||||
"AOFFI is not implemented");
|
||||
UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::NDV),
|
||||
"NDV is not implemented");
|
||||
UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::PTP),
|
||||
"PTP is not implemented");
|
||||
|
||||
if (instr.tld4.UsesMiscMode(TextureMiscMode::NODEP)) {
|
||||
LOG_WARNING(HW_GPU, "TLD4.NODEP implementation is incomplete");
|
||||
}
|
||||
|
||||
const auto texture_type = instr.tld4.texture_type.Value();
|
||||
const bool depth_compare = instr.tld4.UsesMiscMode(TextureMiscMode::DC);
|
||||
const bool is_array = instr.tld4.array != 0;
|
||||
WriteTexInstructionFloat(bb, instr,
|
||||
GetTld4Code(instr, texture_type, depth_compare, is_array));
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TLD4S: {
|
||||
UNIMPLEMENTED_IF_MSG(instr.tld4s.UsesMiscMode(TextureMiscMode::AOFFI),
|
||||
"AOFFI is not implemented");
|
||||
if (instr.tld4s.UsesMiscMode(TextureMiscMode::NODEP)) {
|
||||
LOG_WARNING(HW_GPU, "TLD4S.NODEP implementation is incomplete");
|
||||
}
|
||||
|
||||
const bool depth_compare = instr.tld4s.UsesMiscMode(TextureMiscMode::DC);
|
||||
const Node op_a = GetRegister(instr.gpr8);
|
||||
const Node op_b = GetRegister(instr.gpr20);
|
||||
|
||||
// TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction.
|
||||
std::vector<Node> coords;
|
||||
if (depth_compare) {
|
||||
// Note: TLD4S coordinate encoding works just like TEXS's
|
||||
const Node op_y = GetRegister(instr.gpr8.Value() + 1);
|
||||
coords.push_back(op_a);
|
||||
coords.push_back(op_y);
|
||||
coords.push_back(op_b);
|
||||
} else {
|
||||
coords.push_back(op_a);
|
||||
coords.push_back(op_b);
|
||||
}
|
||||
std::vector<Node> extras;
|
||||
extras.push_back(Immediate(static_cast<u32>(instr.tld4s.component)));
|
||||
|
||||
const auto& sampler =
|
||||
GetSampler(instr.sampler, TextureType::Texture2D, false, depth_compare);
|
||||
|
||||
Node4 values;
|
||||
for (u32 element = 0; element < values.size(); ++element) {
|
||||
auto coords_copy = coords;
|
||||
MetaTexture meta{sampler, {}, {}, extras, element};
|
||||
values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy));
|
||||
}
|
||||
|
||||
WriteTexsInstructionFloat(bb, instr, values);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TXQ: {
|
||||
if (instr.txq.UsesMiscMode(TextureMiscMode::NODEP)) {
|
||||
LOG_WARNING(HW_GPU, "TXQ.NODEP implementation is incomplete");
|
||||
}
|
||||
|
||||
// TODO: The new commits on the texture refactor, change the way samplers work.
|
||||
// Sadly, not all texture instructions specify the type of texture their sampler
|
||||
// uses. This must be fixed at a later instance.
|
||||
const auto& sampler =
|
||||
GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false, false);
|
||||
|
||||
u32 indexer = 0;
|
||||
switch (instr.txq.query_type) {
|
||||
case Tegra::Shader::TextureQueryType::Dimension: {
|
||||
for (u32 element = 0; element < 4; ++element) {
|
||||
if (!instr.txq.IsComponentEnabled(element)) {
|
||||
continue;
|
||||
}
|
||||
MetaTexture meta{sampler, {}, {}, {}, element};
|
||||
const Node value =
|
||||
Operation(OperationCode::TextureQueryDimensions, meta, GetRegister(instr.gpr8));
|
||||
SetTemporal(bb, indexer++, value);
|
||||
}
|
||||
for (u32 i = 0; i < indexer; ++i) {
|
||||
SetRegister(bb, instr.gpr0.Value() + i, GetTemporal(i));
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unhandled texture query type: {}",
|
||||
static_cast<u32>(instr.txq.query_type.Value()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TMML: {
|
||||
UNIMPLEMENTED_IF_MSG(instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV),
|
||||
"NDV is not implemented");
|
||||
|
||||
if (instr.tmml.UsesMiscMode(TextureMiscMode::NODEP)) {
|
||||
LOG_WARNING(HW_GPU, "TMML.NODEP implementation is incomplete");
|
||||
}
|
||||
|
||||
auto texture_type = instr.tmml.texture_type.Value();
|
||||
const bool is_array = instr.tmml.array != 0;
|
||||
const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, false);
|
||||
|
||||
std::vector<Node> coords;
|
||||
|
||||
// TODO: Add coordinates for different samplers once other texture types are implemented.
|
||||
switch (texture_type) {
|
||||
case TextureType::Texture1D:
|
||||
coords.push_back(GetRegister(instr.gpr8));
|
||||
break;
|
||||
case TextureType::Texture2D:
|
||||
coords.push_back(GetRegister(instr.gpr8.Value() + 0));
|
||||
coords.push_back(GetRegister(instr.gpr8.Value() + 1));
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unhandled texture type {}", static_cast<u32>(texture_type));
|
||||
|
||||
// Fallback to interpreting as a 2D texture for now
|
||||
coords.push_back(GetRegister(instr.gpr8.Value() + 0));
|
||||
coords.push_back(GetRegister(instr.gpr8.Value() + 1));
|
||||
texture_type = TextureType::Texture2D;
|
||||
}
|
||||
|
||||
for (u32 element = 0; element < 2; ++element) {
|
||||
auto params = coords;
|
||||
MetaTexture meta{sampler, {}, {}, {}, element};
|
||||
const Node value = Operation(OperationCode::TextureQueryLod, meta, std::move(params));
|
||||
SetTemporal(bb, element, value);
|
||||
}
|
||||
for (u32 element = 0; element < 2; ++element) {
|
||||
SetRegister(bb, instr.gpr0.Value() + element, GetTemporal(element));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TLDS: {
|
||||
const Tegra::Shader::TextureType texture_type{instr.tlds.GetTextureType()};
|
||||
const bool is_array{instr.tlds.IsArrayTexture()};
|
||||
|
||||
UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(TextureMiscMode::AOFFI),
|
||||
"AOFFI is not implemented");
|
||||
UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(TextureMiscMode::MZ), "MZ is not implemented");
|
||||
|
||||
if (instr.tlds.UsesMiscMode(TextureMiscMode::NODEP)) {
|
||||
LOG_WARNING(HW_GPU, "TLDS.NODEP implementation is incomplete");
|
||||
}
|
||||
|
||||
WriteTexsInstructionFloat(bb, instr, GetTldsCode(instr, texture_type, is_array));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unhandled memory instruction: {}", opcode->get().GetName());
|
||||
}
|
||||
@@ -442,291 +236,4 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Sampler& ShaderIR::GetSampler(const Tegra::Shader::Sampler& sampler, TextureType type,
|
||||
bool is_array, bool is_shadow) {
|
||||
const auto offset = static_cast<std::size_t>(sampler.index.Value());
|
||||
|
||||
// If this sampler has already been used, return the existing mapping.
|
||||
const auto itr =
|
||||
std::find_if(used_samplers.begin(), used_samplers.end(),
|
||||
[&](const Sampler& entry) { return entry.GetOffset() == offset; });
|
||||
if (itr != used_samplers.end()) {
|
||||
ASSERT(itr->GetType() == type && itr->IsArray() == is_array &&
|
||||
itr->IsShadow() == is_shadow);
|
||||
return *itr;
|
||||
}
|
||||
|
||||
// Otherwise create a new mapping for this sampler
|
||||
const std::size_t next_index = used_samplers.size();
|
||||
const Sampler entry{offset, next_index, type, is_array, is_shadow};
|
||||
return *used_samplers.emplace(entry).first;
|
||||
}
|
||||
|
||||
void ShaderIR::WriteTexInstructionFloat(NodeBlock& bb, Instruction instr, const Node4& components) {
|
||||
u32 dest_elem = 0;
|
||||
for (u32 elem = 0; elem < 4; ++elem) {
|
||||
if (!instr.tex.IsComponentEnabled(elem)) {
|
||||
// Skip disabled components
|
||||
continue;
|
||||
}
|
||||
SetTemporal(bb, dest_elem++, components[elem]);
|
||||
}
|
||||
// After writing values in temporals, move them to the real registers
|
||||
for (u32 i = 0; i < dest_elem; ++i) {
|
||||
SetRegister(bb, instr.gpr0.Value() + i, GetTemporal(i));
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderIR::WriteTexsInstructionFloat(NodeBlock& bb, Instruction instr,
|
||||
const Node4& components) {
|
||||
// TEXS has two destination registers and a swizzle. The first two elements in the swizzle
|
||||
// go into gpr0+0 and gpr0+1, and the rest goes into gpr28+0 and gpr28+1
|
||||
|
||||
u32 dest_elem = 0;
|
||||
for (u32 component = 0; component < 4; ++component) {
|
||||
if (!instr.texs.IsComponentEnabled(component))
|
||||
continue;
|
||||
SetTemporal(bb, dest_elem++, components[component]);
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < dest_elem; ++i) {
|
||||
if (i < 2) {
|
||||
// Write the first two swizzle components to gpr0 and gpr0+1
|
||||
SetRegister(bb, instr.gpr0.Value() + i % 2, GetTemporal(i));
|
||||
} else {
|
||||
ASSERT(instr.texs.HasTwoDestinations());
|
||||
// Write the rest of the swizzle components to gpr28 and gpr28+1
|
||||
SetRegister(bb, instr.gpr28.Value() + i % 2, GetTemporal(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderIR::WriteTexsInstructionHalfFloat(NodeBlock& bb, Instruction instr,
|
||||
const Node4& components) {
|
||||
// TEXS.F16 destionation registers are packed in two registers in pairs (just like any half
|
||||
// float instruction).
|
||||
|
||||
Node4 values;
|
||||
u32 dest_elem = 0;
|
||||
for (u32 component = 0; component < 4; ++component) {
|
||||
if (!instr.texs.IsComponentEnabled(component))
|
||||
continue;
|
||||
values[dest_elem++] = components[component];
|
||||
}
|
||||
if (dest_elem == 0)
|
||||
return;
|
||||
|
||||
std::generate(values.begin() + dest_elem, values.end(), [&]() { return Immediate(0); });
|
||||
|
||||
const Node first_value = Operation(OperationCode::HPack2, values[0], values[1]);
|
||||
if (dest_elem <= 2) {
|
||||
SetRegister(bb, instr.gpr0, first_value);
|
||||
return;
|
||||
}
|
||||
|
||||
SetTemporal(bb, 0, first_value);
|
||||
SetTemporal(bb, 1, Operation(OperationCode::HPack2, values[2], values[3]));
|
||||
|
||||
SetRegister(bb, instr.gpr0, GetTemporal(0));
|
||||
SetRegister(bb, instr.gpr28, GetTemporal(1));
|
||||
}
|
||||
|
||||
Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
|
||||
TextureProcessMode process_mode, std::vector<Node> coords,
|
||||
Node array, Node depth_compare, u32 bias_offset) {
|
||||
const bool is_array = array;
|
||||
const bool is_shadow = depth_compare;
|
||||
|
||||
UNIMPLEMENTED_IF_MSG((texture_type == TextureType::Texture3D && (is_array || is_shadow)) ||
|
||||
(texture_type == TextureType::TextureCube && is_array && is_shadow),
|
||||
"This method is not supported.");
|
||||
|
||||
const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, is_shadow);
|
||||
|
||||
const bool lod_needed = process_mode == TextureProcessMode::LZ ||
|
||||
process_mode == TextureProcessMode::LL ||
|
||||
process_mode == TextureProcessMode::LLA;
|
||||
|
||||
// LOD selection (either via bias or explicit textureLod) not supported in GL for
|
||||
// sampler2DArrayShadow and samplerCubeArrayShadow.
|
||||
const bool gl_lod_supported =
|
||||
!((texture_type == Tegra::Shader::TextureType::Texture2D && is_array && is_shadow) ||
|
||||
(texture_type == Tegra::Shader::TextureType::TextureCube && is_array && is_shadow));
|
||||
|
||||
const OperationCode read_method =
|
||||
lod_needed && gl_lod_supported ? OperationCode::TextureLod : OperationCode::Texture;
|
||||
|
||||
UNIMPLEMENTED_IF(process_mode != TextureProcessMode::None && !gl_lod_supported);
|
||||
|
||||
std::vector<Node> extras;
|
||||
if (process_mode != TextureProcessMode::None && gl_lod_supported) {
|
||||
if (process_mode == TextureProcessMode::LZ) {
|
||||
extras.push_back(Immediate(0.0f));
|
||||
} else {
|
||||
// If present, lod or bias are always stored in the register indexed by the gpr20
|
||||
// field with an offset depending on the usage of the other registers
|
||||
extras.push_back(GetRegister(instr.gpr20.Value() + bias_offset));
|
||||
}
|
||||
}
|
||||
|
||||
Node4 values;
|
||||
for (u32 element = 0; element < values.size(); ++element) {
|
||||
auto copy_coords = coords;
|
||||
MetaTexture meta{sampler, array, depth_compare, extras, element};
|
||||
values[element] = Operation(read_method, meta, std::move(copy_coords));
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
Node4 ShaderIR::GetTexCode(Instruction instr, TextureType texture_type,
|
||||
TextureProcessMode process_mode, bool depth_compare, bool is_array) {
|
||||
const bool lod_bias_enabled =
|
||||
(process_mode != TextureProcessMode::None && process_mode != TextureProcessMode::LZ);
|
||||
|
||||
const auto [coord_count, total_coord_count] = ValidateAndGetCoordinateElement(
|
||||
texture_type, depth_compare, is_array, lod_bias_enabled, 4, 5);
|
||||
// If enabled arrays index is always stored in the gpr8 field
|
||||
const u64 array_register = instr.gpr8.Value();
|
||||
// First coordinate index is the gpr8 or gpr8 + 1 when arrays are used
|
||||
const u64 coord_register = array_register + (is_array ? 1 : 0);
|
||||
|
||||
std::vector<Node> coords;
|
||||
for (std::size_t i = 0; i < coord_count; ++i) {
|
||||
coords.push_back(GetRegister(coord_register + i));
|
||||
}
|
||||
// 1D.DC in OpenGL the 2nd component is ignored.
|
||||
if (depth_compare && !is_array && texture_type == TextureType::Texture1D) {
|
||||
coords.push_back(Immediate(0.0f));
|
||||
}
|
||||
|
||||
const Node array = is_array ? GetRegister(array_register) : nullptr;
|
||||
|
||||
Node dc{};
|
||||
if (depth_compare) {
|
||||
// Depth is always stored in the register signaled by gpr20 or in the next register if lod
|
||||
// or bias are used
|
||||
const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0);
|
||||
dc = GetRegister(depth_register);
|
||||
}
|
||||
|
||||
return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, 0);
|
||||
}
|
||||
|
||||
Node4 ShaderIR::GetTexsCode(Instruction instr, TextureType texture_type,
|
||||
TextureProcessMode process_mode, bool depth_compare, bool is_array) {
|
||||
const bool lod_bias_enabled =
|
||||
(process_mode != TextureProcessMode::None && process_mode != TextureProcessMode::LZ);
|
||||
|
||||
const auto [coord_count, total_coord_count] = ValidateAndGetCoordinateElement(
|
||||
texture_type, depth_compare, is_array, lod_bias_enabled, 4, 4);
|
||||
// If enabled arrays index is always stored in the gpr8 field
|
||||
const u64 array_register = instr.gpr8.Value();
|
||||
// First coordinate index is stored in gpr8 field or (gpr8 + 1) when arrays are used
|
||||
const u64 coord_register = array_register + (is_array ? 1 : 0);
|
||||
const u64 last_coord_register =
|
||||
(is_array || !(lod_bias_enabled || depth_compare) || (coord_count > 2))
|
||||
? static_cast<u64>(instr.gpr20.Value())
|
||||
: coord_register + 1;
|
||||
const u32 bias_offset = coord_count > 2 ? 1 : 0;
|
||||
|
||||
std::vector<Node> coords;
|
||||
for (std::size_t i = 0; i < coord_count; ++i) {
|
||||
const bool last = (i == (coord_count - 1)) && (coord_count > 1);
|
||||
coords.push_back(GetRegister(last ? last_coord_register : coord_register + i));
|
||||
}
|
||||
|
||||
const Node array = is_array ? GetRegister(array_register) : nullptr;
|
||||
|
||||
Node dc{};
|
||||
if (depth_compare) {
|
||||
// Depth is always stored in the register signaled by gpr20 or in the next register if lod
|
||||
// or bias are used
|
||||
const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0);
|
||||
dc = GetRegister(depth_register);
|
||||
}
|
||||
|
||||
return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, bias_offset);
|
||||
}
|
||||
|
||||
Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool depth_compare,
|
||||
bool is_array) {
|
||||
const std::size_t coord_count = GetCoordCount(texture_type);
|
||||
const std::size_t total_coord_count = coord_count + (is_array ? 1 : 0);
|
||||
const std::size_t total_reg_count = total_coord_count + (depth_compare ? 1 : 0);
|
||||
|
||||
// If enabled arrays index is always stored in the gpr8 field
|
||||
const u64 array_register = instr.gpr8.Value();
|
||||
// First coordinate index is the gpr8 or gpr8 + 1 when arrays are used
|
||||
const u64 coord_register = array_register + (is_array ? 1 : 0);
|
||||
|
||||
std::vector<Node> coords;
|
||||
for (size_t i = 0; i < coord_count; ++i)
|
||||
coords.push_back(GetRegister(coord_register + i));
|
||||
|
||||
const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, depth_compare);
|
||||
|
||||
Node4 values;
|
||||
for (u32 element = 0; element < values.size(); ++element) {
|
||||
auto coords_copy = coords;
|
||||
MetaTexture meta{sampler, GetRegister(array_register), {}, {}, element};
|
||||
values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy));
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
Node4 ShaderIR::GetTldsCode(Instruction instr, TextureType texture_type, bool is_array) {
|
||||
const std::size_t type_coord_count = GetCoordCount(texture_type);
|
||||
const bool lod_enabled = instr.tlds.GetTextureProcessMode() == TextureProcessMode::LL;
|
||||
|
||||
// If enabled arrays index is always stored in the gpr8 field
|
||||
const u64 array_register = instr.gpr8.Value();
|
||||
// if is array gpr20 is used
|
||||
const u64 coord_register = is_array ? instr.gpr20.Value() : instr.gpr8.Value();
|
||||
|
||||
const u64 last_coord_register =
|
||||
((type_coord_count > 2) || (type_coord_count == 2 && !lod_enabled)) && !is_array
|
||||
? static_cast<u64>(instr.gpr20.Value())
|
||||
: coord_register + 1;
|
||||
|
||||
std::vector<Node> coords;
|
||||
for (std::size_t i = 0; i < type_coord_count; ++i) {
|
||||
const bool last = (i == (type_coord_count - 1)) && (type_coord_count > 1);
|
||||
coords.push_back(GetRegister(last ? last_coord_register : coord_register + i));
|
||||
}
|
||||
|
||||
const Node array = is_array ? GetRegister(array_register) : nullptr;
|
||||
// When lod is used always is in gpr20
|
||||
const Node lod = lod_enabled ? GetRegister(instr.gpr20) : Immediate(0);
|
||||
|
||||
const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, false);
|
||||
|
||||
Node4 values;
|
||||
for (u32 element = 0; element < values.size(); ++element) {
|
||||
auto coords_copy = coords;
|
||||
MetaTexture meta{sampler, array, {}, {lod}, element};
|
||||
values[element] = Operation(OperationCode::TexelFetch, meta, std::move(coords_copy));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
std::tuple<std::size_t, std::size_t> ShaderIR::ValidateAndGetCoordinateElement(
|
||||
TextureType texture_type, bool depth_compare, bool is_array, bool lod_bias_enabled,
|
||||
std::size_t max_coords, std::size_t max_inputs) {
|
||||
const std::size_t coord_count = GetCoordCount(texture_type);
|
||||
|
||||
std::size_t total_coord_count = coord_count + (is_array ? 1 : 0) + (depth_compare ? 1 : 0);
|
||||
const std::size_t total_reg_count = total_coord_count + (lod_bias_enabled ? 1 : 0);
|
||||
if (total_coord_count > max_coords || total_reg_count > max_inputs) {
|
||||
UNIMPLEMENTED_MSG("Unsupported Texture operation");
|
||||
total_coord_count = std::min(total_coord_count, max_coords);
|
||||
}
|
||||
// 1D.DC OpenGL is using a vec3 but 2nd component is ignored later.
|
||||
total_coord_count +=
|
||||
(depth_compare && !is_array && texture_type == TextureType::Texture1D) ? 1 : 0;
|
||||
|
||||
return {coord_count, total_coord_count};
|
||||
}
|
||||
|
||||
} // namespace VideoCommon::Shader
|
||||
|
||||
534
src/video_core/shader/decode/texture.cpp
Normal file
534
src/video_core/shader/decode/texture.cpp
Normal file
@@ -0,0 +1,534 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/engines/shader_bytecode.h"
|
||||
#include "video_core/shader/shader_ir.h"
|
||||
|
||||
namespace VideoCommon::Shader {
|
||||
|
||||
using Tegra::Shader::Instruction;
|
||||
using Tegra::Shader::OpCode;
|
||||
using Tegra::Shader::Register;
|
||||
using Tegra::Shader::TextureMiscMode;
|
||||
using Tegra::Shader::TextureProcessMode;
|
||||
using Tegra::Shader::TextureType;
|
||||
|
||||
static std::size_t GetCoordCount(TextureType texture_type) {
|
||||
switch (texture_type) {
|
||||
case TextureType::Texture1D:
|
||||
return 1;
|
||||
case TextureType::Texture2D:
|
||||
return 2;
|
||||
case TextureType::Texture3D:
|
||||
case TextureType::TextureCube:
|
||||
return 3;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unhandled texture type: {}", static_cast<u32>(texture_type));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
|
||||
const Instruction instr = {program_code[pc]};
|
||||
const auto opcode = OpCode::Decode(instr);
|
||||
|
||||
switch (opcode->get().GetId()) {
|
||||
case OpCode::Id::TEX: {
|
||||
UNIMPLEMENTED_IF_MSG(instr.tex.UsesMiscMode(TextureMiscMode::AOFFI),
|
||||
"AOFFI is not implemented");
|
||||
|
||||
if (instr.tex.UsesMiscMode(TextureMiscMode::NODEP)) {
|
||||
LOG_WARNING(HW_GPU, "TEX.NODEP implementation is incomplete");
|
||||
}
|
||||
|
||||
const TextureType texture_type{instr.tex.texture_type};
|
||||
const bool is_array = instr.tex.array != 0;
|
||||
const bool depth_compare = instr.tex.UsesMiscMode(TextureMiscMode::DC);
|
||||
const auto process_mode = instr.tex.GetTextureProcessMode();
|
||||
WriteTexInstructionFloat(
|
||||
bb, instr, GetTexCode(instr, texture_type, process_mode, depth_compare, is_array));
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TEXS: {
|
||||
const TextureType texture_type{instr.texs.GetTextureType()};
|
||||
const bool is_array{instr.texs.IsArrayTexture()};
|
||||
const bool depth_compare = instr.texs.UsesMiscMode(TextureMiscMode::DC);
|
||||
const auto process_mode = instr.texs.GetTextureProcessMode();
|
||||
|
||||
if (instr.texs.UsesMiscMode(TextureMiscMode::NODEP)) {
|
||||
LOG_WARNING(HW_GPU, "TEXS.NODEP implementation is incomplete");
|
||||
}
|
||||
|
||||
const Node4 components =
|
||||
GetTexsCode(instr, texture_type, process_mode, depth_compare, is_array);
|
||||
|
||||
if (instr.texs.fp32_flag) {
|
||||
WriteTexsInstructionFloat(bb, instr, components);
|
||||
} else {
|
||||
WriteTexsInstructionHalfFloat(bb, instr, components);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TLD4: {
|
||||
ASSERT(instr.tld4.array == 0);
|
||||
UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::AOFFI),
|
||||
"AOFFI is not implemented");
|
||||
UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::NDV),
|
||||
"NDV is not implemented");
|
||||
UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(TextureMiscMode::PTP),
|
||||
"PTP is not implemented");
|
||||
|
||||
if (instr.tld4.UsesMiscMode(TextureMiscMode::NODEP)) {
|
||||
LOG_WARNING(HW_GPU, "TLD4.NODEP implementation is incomplete");
|
||||
}
|
||||
|
||||
const auto texture_type = instr.tld4.texture_type.Value();
|
||||
const bool depth_compare = instr.tld4.UsesMiscMode(TextureMiscMode::DC);
|
||||
const bool is_array = instr.tld4.array != 0;
|
||||
WriteTexInstructionFloat(bb, instr,
|
||||
GetTld4Code(instr, texture_type, depth_compare, is_array));
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TLD4S: {
|
||||
UNIMPLEMENTED_IF_MSG(instr.tld4s.UsesMiscMode(TextureMiscMode::AOFFI),
|
||||
"AOFFI is not implemented");
|
||||
if (instr.tld4s.UsesMiscMode(TextureMiscMode::NODEP)) {
|
||||
LOG_WARNING(HW_GPU, "TLD4S.NODEP implementation is incomplete");
|
||||
}
|
||||
|
||||
const bool depth_compare = instr.tld4s.UsesMiscMode(TextureMiscMode::DC);
|
||||
const Node op_a = GetRegister(instr.gpr8);
|
||||
const Node op_b = GetRegister(instr.gpr20);
|
||||
|
||||
// TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction.
|
||||
std::vector<Node> coords;
|
||||
if (depth_compare) {
|
||||
// Note: TLD4S coordinate encoding works just like TEXS's
|
||||
const Node op_y = GetRegister(instr.gpr8.Value() + 1);
|
||||
coords.push_back(op_a);
|
||||
coords.push_back(op_y);
|
||||
coords.push_back(op_b);
|
||||
} else {
|
||||
coords.push_back(op_a);
|
||||
coords.push_back(op_b);
|
||||
}
|
||||
const Node component = Immediate(static_cast<u32>(instr.tld4s.component));
|
||||
|
||||
const auto& sampler =
|
||||
GetSampler(instr.sampler, TextureType::Texture2D, false, depth_compare);
|
||||
|
||||
Node4 values;
|
||||
for (u32 element = 0; element < values.size(); ++element) {
|
||||
auto coords_copy = coords;
|
||||
MetaTexture meta{sampler, {}, {}, {}, {}, component, element};
|
||||
values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy));
|
||||
}
|
||||
|
||||
WriteTexsInstructionFloat(bb, instr, values);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TXQ: {
|
||||
if (instr.txq.UsesMiscMode(TextureMiscMode::NODEP)) {
|
||||
LOG_WARNING(HW_GPU, "TXQ.NODEP implementation is incomplete");
|
||||
}
|
||||
|
||||
// TODO: The new commits on the texture refactor, change the way samplers work.
|
||||
// Sadly, not all texture instructions specify the type of texture their sampler
|
||||
// uses. This must be fixed at a later instance.
|
||||
const auto& sampler =
|
||||
GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false, false);
|
||||
|
||||
u32 indexer = 0;
|
||||
switch (instr.txq.query_type) {
|
||||
case Tegra::Shader::TextureQueryType::Dimension: {
|
||||
for (u32 element = 0; element < 4; ++element) {
|
||||
if (!instr.txq.IsComponentEnabled(element)) {
|
||||
continue;
|
||||
}
|
||||
MetaTexture meta{sampler, {}, {}, {}, {}, {}, element};
|
||||
const Node value =
|
||||
Operation(OperationCode::TextureQueryDimensions, meta, GetRegister(instr.gpr8));
|
||||
SetTemporal(bb, indexer++, value);
|
||||
}
|
||||
for (u32 i = 0; i < indexer; ++i) {
|
||||
SetRegister(bb, instr.gpr0.Value() + i, GetTemporal(i));
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unhandled texture query type: {}",
|
||||
static_cast<u32>(instr.txq.query_type.Value()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TMML: {
|
||||
UNIMPLEMENTED_IF_MSG(instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV),
|
||||
"NDV is not implemented");
|
||||
|
||||
if (instr.tmml.UsesMiscMode(TextureMiscMode::NODEP)) {
|
||||
LOG_WARNING(HW_GPU, "TMML.NODEP implementation is incomplete");
|
||||
}
|
||||
|
||||
auto texture_type = instr.tmml.texture_type.Value();
|
||||
const bool is_array = instr.tmml.array != 0;
|
||||
const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, false);
|
||||
|
||||
std::vector<Node> coords;
|
||||
|
||||
// TODO: Add coordinates for different samplers once other texture types are implemented.
|
||||
switch (texture_type) {
|
||||
case TextureType::Texture1D:
|
||||
coords.push_back(GetRegister(instr.gpr8));
|
||||
break;
|
||||
case TextureType::Texture2D:
|
||||
coords.push_back(GetRegister(instr.gpr8.Value() + 0));
|
||||
coords.push_back(GetRegister(instr.gpr8.Value() + 1));
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unhandled texture type {}", static_cast<u32>(texture_type));
|
||||
|
||||
// Fallback to interpreting as a 2D texture for now
|
||||
coords.push_back(GetRegister(instr.gpr8.Value() + 0));
|
||||
coords.push_back(GetRegister(instr.gpr8.Value() + 1));
|
||||
texture_type = TextureType::Texture2D;
|
||||
}
|
||||
|
||||
for (u32 element = 0; element < 2; ++element) {
|
||||
auto params = coords;
|
||||
MetaTexture meta{sampler, {}, {}, {}, {}, {}, element};
|
||||
const Node value = Operation(OperationCode::TextureQueryLod, meta, std::move(params));
|
||||
SetTemporal(bb, element, value);
|
||||
}
|
||||
for (u32 element = 0; element < 2; ++element) {
|
||||
SetRegister(bb, instr.gpr0.Value() + element, GetTemporal(element));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TLDS: {
|
||||
const Tegra::Shader::TextureType texture_type{instr.tlds.GetTextureType()};
|
||||
const bool is_array{instr.tlds.IsArrayTexture()};
|
||||
|
||||
UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(TextureMiscMode::AOFFI),
|
||||
"AOFFI is not implemented");
|
||||
UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(TextureMiscMode::MZ), "MZ is not implemented");
|
||||
|
||||
if (instr.tlds.UsesMiscMode(TextureMiscMode::NODEP)) {
|
||||
LOG_WARNING(HW_GPU, "TLDS.NODEP implementation is incomplete");
|
||||
}
|
||||
|
||||
WriteTexsInstructionFloat(bb, instr, GetTldsCode(instr, texture_type, is_array));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unhandled memory instruction: {}", opcode->get().GetName());
|
||||
}
|
||||
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Sampler& ShaderIR::GetSampler(const Tegra::Shader::Sampler& sampler, TextureType type,
|
||||
bool is_array, bool is_shadow) {
|
||||
const auto offset = static_cast<std::size_t>(sampler.index.Value());
|
||||
|
||||
// If this sampler has already been used, return the existing mapping.
|
||||
const auto itr =
|
||||
std::find_if(used_samplers.begin(), used_samplers.end(),
|
||||
[&](const Sampler& entry) { return entry.GetOffset() == offset; });
|
||||
if (itr != used_samplers.end()) {
|
||||
ASSERT(itr->GetType() == type && itr->IsArray() == is_array &&
|
||||
itr->IsShadow() == is_shadow);
|
||||
return *itr;
|
||||
}
|
||||
|
||||
// Otherwise create a new mapping for this sampler
|
||||
const std::size_t next_index = used_samplers.size();
|
||||
const Sampler entry{offset, next_index, type, is_array, is_shadow};
|
||||
return *used_samplers.emplace(entry).first;
|
||||
}
|
||||
|
||||
void ShaderIR::WriteTexInstructionFloat(NodeBlock& bb, Instruction instr, const Node4& components) {
|
||||
u32 dest_elem = 0;
|
||||
for (u32 elem = 0; elem < 4; ++elem) {
|
||||
if (!instr.tex.IsComponentEnabled(elem)) {
|
||||
// Skip disabled components
|
||||
continue;
|
||||
}
|
||||
SetTemporal(bb, dest_elem++, components[elem]);
|
||||
}
|
||||
// After writing values in temporals, move them to the real registers
|
||||
for (u32 i = 0; i < dest_elem; ++i) {
|
||||
SetRegister(bb, instr.gpr0.Value() + i, GetTemporal(i));
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderIR::WriteTexsInstructionFloat(NodeBlock& bb, Instruction instr,
|
||||
const Node4& components) {
|
||||
// TEXS has two destination registers and a swizzle. The first two elements in the swizzle
|
||||
// go into gpr0+0 and gpr0+1, and the rest goes into gpr28+0 and gpr28+1
|
||||
|
||||
u32 dest_elem = 0;
|
||||
for (u32 component = 0; component < 4; ++component) {
|
||||
if (!instr.texs.IsComponentEnabled(component))
|
||||
continue;
|
||||
SetTemporal(bb, dest_elem++, components[component]);
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < dest_elem; ++i) {
|
||||
if (i < 2) {
|
||||
// Write the first two swizzle components to gpr0 and gpr0+1
|
||||
SetRegister(bb, instr.gpr0.Value() + i % 2, GetTemporal(i));
|
||||
} else {
|
||||
ASSERT(instr.texs.HasTwoDestinations());
|
||||
// Write the rest of the swizzle components to gpr28 and gpr28+1
|
||||
SetRegister(bb, instr.gpr28.Value() + i % 2, GetTemporal(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderIR::WriteTexsInstructionHalfFloat(NodeBlock& bb, Instruction instr,
|
||||
const Node4& components) {
|
||||
// TEXS.F16 destionation registers are packed in two registers in pairs (just like any half
|
||||
// float instruction).
|
||||
|
||||
Node4 values;
|
||||
u32 dest_elem = 0;
|
||||
for (u32 component = 0; component < 4; ++component) {
|
||||
if (!instr.texs.IsComponentEnabled(component))
|
||||
continue;
|
||||
values[dest_elem++] = components[component];
|
||||
}
|
||||
if (dest_elem == 0)
|
||||
return;
|
||||
|
||||
std::generate(values.begin() + dest_elem, values.end(), [&]() { return Immediate(0); });
|
||||
|
||||
const Node first_value = Operation(OperationCode::HPack2, values[0], values[1]);
|
||||
if (dest_elem <= 2) {
|
||||
SetRegister(bb, instr.gpr0, first_value);
|
||||
return;
|
||||
}
|
||||
|
||||
SetTemporal(bb, 0, first_value);
|
||||
SetTemporal(bb, 1, Operation(OperationCode::HPack2, values[2], values[3]));
|
||||
|
||||
SetRegister(bb, instr.gpr0, GetTemporal(0));
|
||||
SetRegister(bb, instr.gpr28, GetTemporal(1));
|
||||
}
|
||||
|
||||
Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
|
||||
TextureProcessMode process_mode, std::vector<Node> coords,
|
||||
Node array, Node depth_compare, u32 bias_offset) {
|
||||
const bool is_array = array;
|
||||
const bool is_shadow = depth_compare;
|
||||
|
||||
UNIMPLEMENTED_IF_MSG((texture_type == TextureType::Texture3D && (is_array || is_shadow)) ||
|
||||
(texture_type == TextureType::TextureCube && is_array && is_shadow),
|
||||
"This method is not supported.");
|
||||
|
||||
const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, is_shadow);
|
||||
|
||||
const bool lod_needed = process_mode == TextureProcessMode::LZ ||
|
||||
process_mode == TextureProcessMode::LL ||
|
||||
process_mode == TextureProcessMode::LLA;
|
||||
|
||||
// LOD selection (either via bias or explicit textureLod) not supported in GL for
|
||||
// sampler2DArrayShadow and samplerCubeArrayShadow.
|
||||
const bool gl_lod_supported =
|
||||
!((texture_type == Tegra::Shader::TextureType::Texture2D && is_array && is_shadow) ||
|
||||
(texture_type == Tegra::Shader::TextureType::TextureCube && is_array && is_shadow));
|
||||
|
||||
const OperationCode read_method =
|
||||
(lod_needed && gl_lod_supported) ? OperationCode::TextureLod : OperationCode::Texture;
|
||||
|
||||
UNIMPLEMENTED_IF(process_mode != TextureProcessMode::None && !gl_lod_supported);
|
||||
|
||||
Node bias = {};
|
||||
Node lod = {};
|
||||
if (process_mode != TextureProcessMode::None && gl_lod_supported) {
|
||||
switch (process_mode) {
|
||||
case TextureProcessMode::LZ:
|
||||
lod = Immediate(0.0f);
|
||||
break;
|
||||
case TextureProcessMode::LB:
|
||||
// If present, lod or bias are always stored in the register indexed by the gpr20
|
||||
// field with an offset depending on the usage of the other registers
|
||||
bias = GetRegister(instr.gpr20.Value() + bias_offset);
|
||||
break;
|
||||
case TextureProcessMode::LL:
|
||||
lod = GetRegister(instr.gpr20.Value() + bias_offset);
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented process mode={}", static_cast<u32>(process_mode));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Node4 values;
|
||||
for (u32 element = 0; element < values.size(); ++element) {
|
||||
auto copy_coords = coords;
|
||||
MetaTexture meta{sampler, array, depth_compare, bias, lod, {}, element};
|
||||
values[element] = Operation(read_method, meta, std::move(copy_coords));
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
Node4 ShaderIR::GetTexCode(Instruction instr, TextureType texture_type,
|
||||
TextureProcessMode process_mode, bool depth_compare, bool is_array) {
|
||||
const bool lod_bias_enabled =
|
||||
(process_mode != TextureProcessMode::None && process_mode != TextureProcessMode::LZ);
|
||||
|
||||
const auto [coord_count, total_coord_count] = ValidateAndGetCoordinateElement(
|
||||
texture_type, depth_compare, is_array, lod_bias_enabled, 4, 5);
|
||||
// If enabled arrays index is always stored in the gpr8 field
|
||||
const u64 array_register = instr.gpr8.Value();
|
||||
// First coordinate index is the gpr8 or gpr8 + 1 when arrays are used
|
||||
const u64 coord_register = array_register + (is_array ? 1 : 0);
|
||||
|
||||
std::vector<Node> coords;
|
||||
for (std::size_t i = 0; i < coord_count; ++i) {
|
||||
coords.push_back(GetRegister(coord_register + i));
|
||||
}
|
||||
// 1D.DC in OpenGL the 2nd component is ignored.
|
||||
if (depth_compare && !is_array && texture_type == TextureType::Texture1D) {
|
||||
coords.push_back(Immediate(0.0f));
|
||||
}
|
||||
|
||||
const Node array = is_array ? GetRegister(array_register) : nullptr;
|
||||
|
||||
Node dc{};
|
||||
if (depth_compare) {
|
||||
// Depth is always stored in the register signaled by gpr20 or in the next register if lod
|
||||
// or bias are used
|
||||
const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0);
|
||||
dc = GetRegister(depth_register);
|
||||
}
|
||||
|
||||
return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, 0);
|
||||
}
|
||||
|
||||
Node4 ShaderIR::GetTexsCode(Instruction instr, TextureType texture_type,
|
||||
TextureProcessMode process_mode, bool depth_compare, bool is_array) {
|
||||
const bool lod_bias_enabled =
|
||||
(process_mode != TextureProcessMode::None && process_mode != TextureProcessMode::LZ);
|
||||
|
||||
const auto [coord_count, total_coord_count] = ValidateAndGetCoordinateElement(
|
||||
texture_type, depth_compare, is_array, lod_bias_enabled, 4, 4);
|
||||
// If enabled arrays index is always stored in the gpr8 field
|
||||
const u64 array_register = instr.gpr8.Value();
|
||||
// First coordinate index is stored in gpr8 field or (gpr8 + 1) when arrays are used
|
||||
const u64 coord_register = array_register + (is_array ? 1 : 0);
|
||||
const u64 last_coord_register =
|
||||
(is_array || !(lod_bias_enabled || depth_compare) || (coord_count > 2))
|
||||
? static_cast<u64>(instr.gpr20.Value())
|
||||
: coord_register + 1;
|
||||
const u32 bias_offset = coord_count > 2 ? 1 : 0;
|
||||
|
||||
std::vector<Node> coords;
|
||||
for (std::size_t i = 0; i < coord_count; ++i) {
|
||||
const bool last = (i == (coord_count - 1)) && (coord_count > 1);
|
||||
coords.push_back(GetRegister(last ? last_coord_register : coord_register + i));
|
||||
}
|
||||
|
||||
const Node array = is_array ? GetRegister(array_register) : nullptr;
|
||||
|
||||
Node dc{};
|
||||
if (depth_compare) {
|
||||
// Depth is always stored in the register signaled by gpr20 or in the next register if lod
|
||||
// or bias are used
|
||||
const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0);
|
||||
dc = GetRegister(depth_register);
|
||||
}
|
||||
|
||||
return GetTextureCode(instr, texture_type, process_mode, coords, array, dc, bias_offset);
|
||||
}
|
||||
|
||||
Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool depth_compare,
|
||||
bool is_array) {
|
||||
const std::size_t coord_count = GetCoordCount(texture_type);
|
||||
const std::size_t total_coord_count = coord_count + (is_array ? 1 : 0);
|
||||
const std::size_t total_reg_count = total_coord_count + (depth_compare ? 1 : 0);
|
||||
|
||||
// If enabled arrays index is always stored in the gpr8 field
|
||||
const u64 array_register = instr.gpr8.Value();
|
||||
// First coordinate index is the gpr8 or gpr8 + 1 when arrays are used
|
||||
const u64 coord_register = array_register + (is_array ? 1 : 0);
|
||||
|
||||
std::vector<Node> coords;
|
||||
for (size_t i = 0; i < coord_count; ++i)
|
||||
coords.push_back(GetRegister(coord_register + i));
|
||||
|
||||
const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, depth_compare);
|
||||
|
||||
Node4 values;
|
||||
for (u32 element = 0; element < values.size(); ++element) {
|
||||
auto coords_copy = coords;
|
||||
MetaTexture meta{sampler, GetRegister(array_register), {}, {}, {}, {}, element};
|
||||
values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy));
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
Node4 ShaderIR::GetTldsCode(Instruction instr, TextureType texture_type, bool is_array) {
|
||||
const std::size_t type_coord_count = GetCoordCount(texture_type);
|
||||
const bool lod_enabled = instr.tlds.GetTextureProcessMode() == TextureProcessMode::LL;
|
||||
|
||||
// If enabled arrays index is always stored in the gpr8 field
|
||||
const u64 array_register = instr.gpr8.Value();
|
||||
// if is array gpr20 is used
|
||||
const u64 coord_register = is_array ? instr.gpr20.Value() : instr.gpr8.Value();
|
||||
|
||||
const u64 last_coord_register =
|
||||
((type_coord_count > 2) || (type_coord_count == 2 && !lod_enabled)) && !is_array
|
||||
? static_cast<u64>(instr.gpr20.Value())
|
||||
: coord_register + 1;
|
||||
|
||||
std::vector<Node> coords;
|
||||
for (std::size_t i = 0; i < type_coord_count; ++i) {
|
||||
const bool last = (i == (type_coord_count - 1)) && (type_coord_count > 1);
|
||||
coords.push_back(GetRegister(last ? last_coord_register : coord_register + i));
|
||||
}
|
||||
|
||||
const Node array = is_array ? GetRegister(array_register) : nullptr;
|
||||
// When lod is used always is in gpr20
|
||||
const Node lod = lod_enabled ? GetRegister(instr.gpr20) : Immediate(0);
|
||||
|
||||
const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, false);
|
||||
|
||||
Node4 values;
|
||||
for (u32 element = 0; element < values.size(); ++element) {
|
||||
auto coords_copy = coords;
|
||||
MetaTexture meta{sampler, array, {}, {}, lod, {}, element};
|
||||
values[element] = Operation(OperationCode::TexelFetch, meta, std::move(coords_copy));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
std::tuple<std::size_t, std::size_t> ShaderIR::ValidateAndGetCoordinateElement(
|
||||
TextureType texture_type, bool depth_compare, bool is_array, bool lod_bias_enabled,
|
||||
std::size_t max_coords, std::size_t max_inputs) {
|
||||
const std::size_t coord_count = GetCoordCount(texture_type);
|
||||
|
||||
std::size_t total_coord_count = coord_count + (is_array ? 1 : 0) + (depth_compare ? 1 : 0);
|
||||
const std::size_t total_reg_count = total_coord_count + (lod_bias_enabled ? 1 : 0);
|
||||
if (total_coord_count > max_coords || total_reg_count > max_inputs) {
|
||||
UNIMPLEMENTED_MSG("Unsupported Texture operation");
|
||||
total_coord_count = std::min(total_coord_count, max_coords);
|
||||
}
|
||||
// 1D.DC OpenGL is using a vec3 but 2nd component is ignored later.
|
||||
total_coord_count +=
|
||||
(depth_compare && !is_array && texture_type == TextureType::Texture1D) ? 1 : 0;
|
||||
|
||||
return {coord_count, total_coord_count};
|
||||
}
|
||||
|
||||
} // namespace VideoCommon::Shader
|
||||
@@ -290,7 +290,9 @@ struct MetaTexture {
|
||||
const Sampler& sampler;
|
||||
Node array{};
|
||||
Node depth_compare{};
|
||||
std::vector<Node> extras;
|
||||
Node bias{};
|
||||
Node lod{};
|
||||
Node component{};
|
||||
u32 element{};
|
||||
};
|
||||
|
||||
@@ -614,6 +616,7 @@ private:
|
||||
u32 DecodeHfma2(NodeBlock& bb, u32 pc);
|
||||
u32 DecodeConversion(NodeBlock& bb, u32 pc);
|
||||
u32 DecodeMemory(NodeBlock& bb, u32 pc);
|
||||
u32 DecodeTexture(NodeBlock& bb, u32 pc);
|
||||
u32 DecodeFloatSetPredicate(NodeBlock& bb, u32 pc);
|
||||
u32 DecodeIntegerSetPredicate(NodeBlock& bb, u32 pc);
|
||||
u32 DecodeHalfSetPredicate(NodeBlock& bb, u32 pc);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
@@ -282,34 +283,62 @@ 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> mip_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;
|
||||
};
|
||||
float border_color_r;
|
||||
float border_color_g;
|
||||
float border_color_b;
|
||||
float border_color_a;
|
||||
|
||||
float GetMaxAnisotropy() const {
|
||||
return static_cast<float>(1U << max_anisotropy);
|
||||
}
|
||||
|
||||
float GetMinLod() const {
|
||||
return static_cast<float>(min_lod_clamp) / 256.0f;
|
||||
}
|
||||
|
||||
float GetMaxLod() const {
|
||||
return static_cast<float>(max_lod_clamp) / 256.0f;
|
||||
}
|
||||
|
||||
float GetLodBias() const {
|
||||
// Sign extend the 13-bit value.
|
||||
constexpr u32 mask = 1U << (13 - 1);
|
||||
return static_cast<s32>((mip_lod_bias ^ mask) - mask) / 256.0f;
|
||||
}
|
||||
|
||||
std::array<float, 4> GetBorderColor() const {
|
||||
if (srgb_conversion) {
|
||||
return {srgb_border_color_r / 255.0f, srgb_border_color_g / 255.0f,
|
||||
srgb_border_color_b / 255.0f, border_color[3]};
|
||||
}
|
||||
return border_color;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(TSCEntry) == 0x20, "TSCEntry has wrong size");
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ add_executable(yuzu
|
||||
configuration/configure_general.h
|
||||
configuration/configure_graphics.cpp
|
||||
configuration/configure_graphics.h
|
||||
configuration/configure_hotkeys.cpp
|
||||
configuration/configure_hotkeys.h
|
||||
configuration/configure_input.cpp
|
||||
configuration/configure_input.h
|
||||
configuration/configure_input_player.cpp
|
||||
@@ -78,6 +80,8 @@ add_executable(yuzu
|
||||
ui_settings.h
|
||||
util/limitable_input_dialog.cpp
|
||||
util/limitable_input_dialog.h
|
||||
util/sequence_dialog/sequence_dialog.cpp
|
||||
util/sequence_dialog/sequence_dialog.h
|
||||
util/spinbox.cpp
|
||||
util/spinbox.h
|
||||
util/util.cpp
|
||||
@@ -95,6 +99,7 @@ set(UIS
|
||||
configuration/configure_gamelist.ui
|
||||
configuration/configure_general.ui
|
||||
configuration/configure_graphics.ui
|
||||
configuration/configure_hotkeys.ui
|
||||
configuration/configure_input.ui
|
||||
configuration/configure_input_player.ui
|
||||
configuration/configure_input_simple.ui
|
||||
@@ -105,7 +110,6 @@ set(UIS
|
||||
configuration/configure_touchscreen_advanced.ui
|
||||
configuration/configure_web.ui
|
||||
compatdb.ui
|
||||
hotkeys.ui
|
||||
loading_screen.ui
|
||||
main.ui
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <mutex>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QScrollArea>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <vector>
|
||||
#include <QDialog>
|
||||
#include <QList>
|
||||
#include <QTreeView>
|
||||
#include "core/frontend/applets/profile_select.h"
|
||||
|
||||
class GMainWindow;
|
||||
@@ -16,7 +17,6 @@ class QLabel;
|
||||
class QScrollArea;
|
||||
class QStandardItem;
|
||||
class QStandardItemModel;
|
||||
class QTreeView;
|
||||
class QVBoxLayout;
|
||||
|
||||
class QtProfileSelectionDialog final : public QDialog {
|
||||
|
||||
@@ -123,7 +123,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);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <QKeySequence>
|
||||
#include <QSettings>
|
||||
#include "common/file_util.h"
|
||||
#include "configure_input_simple.h"
|
||||
@@ -9,7 +11,6 @@
|
||||
#include "core/hle/service/hid/controllers/npad.h"
|
||||
#include "input_common/main.h"
|
||||
#include "yuzu/configuration/config.h"
|
||||
#include "yuzu/ui_settings.h"
|
||||
|
||||
Config::Config() {
|
||||
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
|
||||
@@ -17,7 +18,6 @@ Config::Config() {
|
||||
FileUtil::CreateFullPath(qt_config_loc);
|
||||
qt_config =
|
||||
std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
|
||||
|
||||
Reload();
|
||||
}
|
||||
|
||||
@@ -205,11 +205,32 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default
|
||||
Qt::Key_Control, Qt::Key_Shift, Qt::Key_AltGr, Qt::Key_ApplicationRight,
|
||||
};
|
||||
|
||||
// This shouldn't have anything except static initializers (no functions). So
|
||||
// QKeySequnce(...).toString() is NOT ALLOWED HERE.
|
||||
// This must be in alphabetical order according to action name as it must have the same order as
|
||||
// UISetting::values.shortcuts, which is alphabetically ordered.
|
||||
const std::array<UISettings::Shortcut, 15> Config::default_hotkeys{
|
||||
{{"Capture Screenshot", "Main Window", {"Ctrl+P", Qt::ApplicationShortcut}},
|
||||
{"Continue/Pause Emulation", "Main Window", {"F4", Qt::WindowShortcut}},
|
||||
{"Decrease Speed Limit", "Main Window", {"-", Qt::ApplicationShortcut}},
|
||||
{"Exit yuzu", "Main Window", {"Ctrl+Q", Qt::WindowShortcut}},
|
||||
{"Exit Fullscreen", "Main Window", {"Esc", Qt::WindowShortcut}},
|
||||
{"Fullscreen", "Main Window", {"F11", Qt::WindowShortcut}},
|
||||
{"Increase Speed Limit", "Main Window", {"+", Qt::ApplicationShortcut}},
|
||||
{"Load Amiibo", "Main Window", {"F2", Qt::ApplicationShortcut}},
|
||||
{"Load File", "Main Window", {"Ctrl+O", Qt::WindowShortcut}},
|
||||
{"Restart Emulation", "Main Window", {"F6", Qt::WindowShortcut}},
|
||||
{"Stop Emulation", "Main Window", {"F5", Qt::WindowShortcut}},
|
||||
{"Toggle Filter Bar", "Main Window", {"Ctrl+F", Qt::WindowShortcut}},
|
||||
{"Toggle Speed Limit", "Main Window", {"Ctrl+Z", Qt::ApplicationShortcut}},
|
||||
{"Toggle Status Bar", "Main Window", {"Ctrl+S", Qt::WindowShortcut}},
|
||||
{"Change Docked Mode", "Main Window", {"F10", Qt::ApplicationShortcut}}}};
|
||||
|
||||
void Config::ReadPlayerValues() {
|
||||
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
|
||||
auto& player = Settings::values.players[p];
|
||||
|
||||
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 +290,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 +319,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 +332,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 +348,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 +376,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 +428,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 +459,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,72 +495,64 @@ 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");
|
||||
QStringList groups = qt_config->childGroups();
|
||||
for (auto group : groups) {
|
||||
for (auto [name, group, shortcut] : default_hotkeys) {
|
||||
auto [keyseq, context] = shortcut;
|
||||
qt_config->beginGroup(group);
|
||||
|
||||
QStringList hotkeys = qt_config->childGroups();
|
||||
for (auto hotkey : hotkeys) {
|
||||
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())));
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
qt_config->beginGroup(name);
|
||||
UISettings::values.shortcuts.push_back(
|
||||
{name,
|
||||
group,
|
||||
{ReadSetting("KeySeq", keyseq).toString(), ReadSetting("Context", context).toInt()}});
|
||||
qt_config->endGroup();
|
||||
qt_config->endGroup();
|
||||
}
|
||||
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 +563,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 +646,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 +743,93 @@ 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);
|
||||
// Lengths of UISettings::values.shortcuts & default_hotkeys are same.
|
||||
// However, their ordering must also be the same.
|
||||
for (std::size_t i = 0; i < default_hotkeys.size(); i++) {
|
||||
auto [name, group, shortcut] = UISettings::values.shortcuts[i];
|
||||
qt_config->beginGroup(group);
|
||||
qt_config->beginGroup(name);
|
||||
WriteSetting("KeySeq", shortcut.first, default_hotkeys[i].shortcut.first);
|
||||
WriteSetting("Context", shortcut.second, default_hotkeys[i].shortcut.second);
|
||||
qt_config->endGroup();
|
||||
qt_config->endGroup();
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <string>
|
||||
#include <QVariant>
|
||||
#include "core/settings.h"
|
||||
#include "yuzu/ui_settings.h"
|
||||
|
||||
class QSettings;
|
||||
|
||||
@@ -42,6 +43,13 @@ 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);
|
||||
|
||||
static const std::array<UISettings::Shortcut, 15> default_hotkeys;
|
||||
|
||||
std::unique_ptr<QSettings> qt_config;
|
||||
std::string qt_config_loc;
|
||||
};
|
||||
|
||||
@@ -7,9 +7,15 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>382</width>
|
||||
<height>241</height>
|
||||
<height>650</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>650</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>yuzu Configuration</string>
|
||||
</property>
|
||||
@@ -62,6 +68,11 @@
|
||||
<string>Input</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="ConfigureHotkeys" name="hotkeysTab">
|
||||
<attribute name="title">
|
||||
<string>Hotkeys</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="ConfigureGraphics" name="graphicsTab">
|
||||
<attribute name="title">
|
||||
<string>Graphics</string>
|
||||
@@ -150,6 +161,12 @@
|
||||
<header>configuration/configure_input_simple.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ConfigureHotkeys</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>configuration/configure_hotkeys.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
|
||||
@@ -8,20 +8,22 @@
|
||||
#include "ui_configure.h"
|
||||
#include "yuzu/configuration/config.h"
|
||||
#include "yuzu/configuration/configure_dialog.h"
|
||||
#include "yuzu/configuration/configure_input_player.h"
|
||||
#include "yuzu/hotkeys.h"
|
||||
|
||||
ConfigureDialog::ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry)
|
||||
: QDialog(parent), ui(new Ui::ConfigureDialog) {
|
||||
ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry)
|
||||
: QDialog(parent), registry(registry), ui(new Ui::ConfigureDialog) {
|
||||
ui->setupUi(this);
|
||||
ui->generalTab->PopulateHotkeyList(registry);
|
||||
ui->hotkeysTab->Populate(registry);
|
||||
this->setConfiguration();
|
||||
this->PopulateSelectionList();
|
||||
connect(ui->selectorList, &QListWidget::itemSelectionChanged, this,
|
||||
&ConfigureDialog::UpdateVisibleTabs);
|
||||
|
||||
adjustSize();
|
||||
|
||||
ui->selectorList->setCurrentRow(0);
|
||||
|
||||
// Synchronise lists upon initialisation
|
||||
ui->hotkeysTab->EmitHotkeysChanged();
|
||||
}
|
||||
|
||||
ConfigureDialog::~ConfigureDialog() = default;
|
||||
@@ -34,6 +36,7 @@ void ConfigureDialog::applyConfiguration() {
|
||||
ui->systemTab->applyConfiguration();
|
||||
ui->profileManagerTab->applyConfiguration();
|
||||
ui->inputTab->applyConfiguration();
|
||||
ui->hotkeysTab->applyConfiguration(registry);
|
||||
ui->graphicsTab->applyConfiguration();
|
||||
ui->audioTab->applyConfiguration();
|
||||
ui->debugTab->applyConfiguration();
|
||||
@@ -47,7 +50,7 @@ void ConfigureDialog::PopulateSelectionList() {
|
||||
{{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}},
|
||||
{tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}},
|
||||
{tr("Graphics"), {tr("Graphics")}},
|
||||
{tr("Controls"), {tr("Input")}}}};
|
||||
{tr("Controls"), {tr("Input"), tr("Hotkeys")}}}};
|
||||
|
||||
for (const auto& entry : items) {
|
||||
auto* const item = new QListWidgetItem(entry.first);
|
||||
@@ -66,6 +69,7 @@ void ConfigureDialog::UpdateVisibleTabs() {
|
||||
{tr("System"), ui->systemTab},
|
||||
{tr("Profiles"), ui->profileManagerTab},
|
||||
{tr("Input"), ui->inputTab},
|
||||
{tr("Hotkeys"), ui->hotkeysTab},
|
||||
{tr("Graphics"), ui->graphicsTab},
|
||||
{tr("Audio"), ui->audioTab},
|
||||
{tr("Debug"), ui->debugTab},
|
||||
|
||||
@@ -17,7 +17,7 @@ class ConfigureDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry);
|
||||
explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry);
|
||||
~ConfigureDialog() override;
|
||||
|
||||
void applyConfiguration();
|
||||
@@ -28,4 +28,5 @@ private:
|
||||
void PopulateSelectionList();
|
||||
|
||||
std::unique_ptr<Ui::ConfigureDialog> ui;
|
||||
HotkeyRegistry& registry;
|
||||
};
|
||||
|
||||
@@ -36,10 +36,6 @@ void ConfigureGeneral::setConfiguration() {
|
||||
ui->enable_nfc->setChecked(Settings::values.enable_nfc);
|
||||
}
|
||||
|
||||
void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) {
|
||||
ui->widget->Populate(registry);
|
||||
}
|
||||
|
||||
void ConfigureGeneral::applyConfiguration() {
|
||||
UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked();
|
||||
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
|
||||
|
||||
@@ -20,7 +20,6 @@ public:
|
||||
explicit ConfigureGeneral(QWidget* parent = nullptr);
|
||||
~ConfigureGeneral() override;
|
||||
|
||||
void PopulateHotkeyList(const HotkeyRegistry& registry);
|
||||
void applyConfiguration();
|
||||
|
||||
private:
|
||||
|
||||
@@ -117,22 +117,6 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="HotKeysGroupBox">
|
||||
<property name="title">
|
||||
<string>Hotkeys</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="HotKeysHorizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="HotKeysVerticalLayout">
|
||||
<item>
|
||||
<widget class="GHotkeysDialog" name="widget" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
@@ -150,14 +134,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>GHotkeysDialog</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>hotkeys.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
121
src/yuzu/configuration/configure_hotkeys.cpp
Normal file
121
src/yuzu/configuration/configure_hotkeys.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QStandardItemModel>
|
||||
#include "core/settings.h"
|
||||
#include "ui_configure_hotkeys.h"
|
||||
#include "yuzu/configuration/configure_hotkeys.h"
|
||||
#include "yuzu/hotkeys.h"
|
||||
#include "yuzu/util/sequence_dialog/sequence_dialog.h"
|
||||
|
||||
ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)
|
||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureHotkeys>()) {
|
||||
ui->setupUi(this);
|
||||
setFocusPolicy(Qt::ClickFocus);
|
||||
|
||||
model = new QStandardItemModel(this);
|
||||
model->setColumnCount(3);
|
||||
model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Context")});
|
||||
|
||||
connect(ui->hotkey_list, &QTreeView::doubleClicked, this, &ConfigureHotkeys::Configure);
|
||||
ui->hotkey_list->setModel(model);
|
||||
|
||||
// TODO(Kloen): Make context configurable as well (hiding the column for now)
|
||||
ui->hotkey_list->hideColumn(2);
|
||||
|
||||
ui->hotkey_list->setColumnWidth(0, 200);
|
||||
ui->hotkey_list->resizeColumnToContents(1);
|
||||
}
|
||||
|
||||
ConfigureHotkeys::~ConfigureHotkeys() = default;
|
||||
|
||||
void ConfigureHotkeys::EmitHotkeysChanged() {
|
||||
emit HotkeysChanged(GetUsedKeyList());
|
||||
}
|
||||
|
||||
QList<QKeySequence> ConfigureHotkeys::GetUsedKeyList() const {
|
||||
QList<QKeySequence> list;
|
||||
for (int r = 0; r < model->rowCount(); r++) {
|
||||
const QStandardItem* parent = model->item(r, 0);
|
||||
for (int r2 = 0; r2 < parent->rowCount(); r2++) {
|
||||
const QStandardItem* keyseq = parent->child(r2, 1);
|
||||
list << QKeySequence::fromString(keyseq->text(), QKeySequence::NativeText);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) {
|
||||
for (const auto& group : registry.hotkey_groups) {
|
||||
auto* parent_item = new QStandardItem(group.first);
|
||||
parent_item->setEditable(false);
|
||||
for (const auto& hotkey : group.second) {
|
||||
auto* action = new QStandardItem(hotkey.first);
|
||||
auto* keyseq =
|
||||
new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText));
|
||||
action->setEditable(false);
|
||||
keyseq->setEditable(false);
|
||||
parent_item->appendRow({action, keyseq});
|
||||
}
|
||||
model->appendRow(parent_item);
|
||||
}
|
||||
|
||||
ui->hotkey_list->expandAll();
|
||||
}
|
||||
|
||||
void ConfigureHotkeys::Configure(QModelIndex index) {
|
||||
if (index.parent() == QModelIndex())
|
||||
return;
|
||||
|
||||
index = index.sibling(index.row(), 1);
|
||||
auto* model = ui->hotkey_list->model();
|
||||
auto previous_key = model->data(index);
|
||||
|
||||
auto* hotkey_dialog = new SequenceDialog;
|
||||
int return_code = hotkey_dialog->exec();
|
||||
|
||||
auto key_sequence = hotkey_dialog->GetSequence();
|
||||
|
||||
if (return_code == QDialog::Rejected || key_sequence.isEmpty())
|
||||
return;
|
||||
|
||||
if (IsUsedKey(key_sequence) && key_sequence != QKeySequence(previous_key.toString())) {
|
||||
QMessageBox::critical(this, tr("Error in inputted key"),
|
||||
tr("You're using a key that's already bound."));
|
||||
} else {
|
||||
model->setData(index, key_sequence.toString(QKeySequence::NativeText));
|
||||
EmitHotkeysChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) {
|
||||
return GetUsedKeyList().contains(key_sequence);
|
||||
}
|
||||
|
||||
void ConfigureHotkeys::applyConfiguration(HotkeyRegistry& registry) {
|
||||
for (int key_id = 0; key_id < model->rowCount(); key_id++) {
|
||||
const QStandardItem* parent = model->item(key_id, 0);
|
||||
for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) {
|
||||
const QStandardItem* action = parent->child(key_column_id, 0);
|
||||
const QStandardItem* keyseq = parent->child(key_column_id, 1);
|
||||
for (auto& [group, sub_actions] : registry.hotkey_groups) {
|
||||
if (group != parent->text())
|
||||
continue;
|
||||
for (auto& [action_name, hotkey] : sub_actions) {
|
||||
if (action_name != action->text())
|
||||
continue;
|
||||
hotkey.keyseq = QKeySequence(keyseq->text());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registry.SaveHotkeys();
|
||||
Settings::Apply();
|
||||
}
|
||||
|
||||
void ConfigureHotkeys::retranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
48
src/yuzu/configuration/configure_hotkeys.h
Normal file
48
src/yuzu/configuration/configure_hotkeys.h
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QWidget>
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Ui {
|
||||
class ConfigureHotkeys;
|
||||
}
|
||||
|
||||
class HotkeyRegistry;
|
||||
class QStandardItemModel;
|
||||
|
||||
class ConfigureHotkeys : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ConfigureHotkeys(QWidget* parent = nullptr);
|
||||
~ConfigureHotkeys() override;
|
||||
|
||||
void applyConfiguration(HotkeyRegistry& registry);
|
||||
void retranslateUi();
|
||||
|
||||
void EmitHotkeysChanged();
|
||||
|
||||
/**
|
||||
* Populates the hotkey list widget using data from the provided registry.
|
||||
* Called everytime the Configure dialog is opened.
|
||||
* @param registry The HotkeyRegistry whose data is used to populate the list.
|
||||
*/
|
||||
void Populate(const HotkeyRegistry& registry);
|
||||
|
||||
signals:
|
||||
void HotkeysChanged(QList<QKeySequence> new_key_list);
|
||||
|
||||
private:
|
||||
void Configure(QModelIndex index);
|
||||
bool IsUsedKey(QKeySequence key_sequence);
|
||||
QList<QKeySequence> GetUsedKeyList() const;
|
||||
|
||||
std::unique_ptr<Ui::ConfigureHotkeys> ui;
|
||||
|
||||
QStandardItemModel* model;
|
||||
};
|
||||
42
src/yuzu/configuration/configure_hotkeys.ui
Normal file
42
src/yuzu/configuration/configure_hotkeys.ui
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ConfigureHotkeys</class>
|
||||
<widget class="QWidget" name="ConfigureHotkeys">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>363</width>
|
||||
<height>388</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Hotkey Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Double-click on a binding to change it.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeView" name="hotkey_list">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <map>
|
||||
#include <QKeySequence>
|
||||
#include <QShortcut>
|
||||
#include <QTreeWidgetItem>
|
||||
@@ -13,47 +12,32 @@
|
||||
HotkeyRegistry::HotkeyRegistry() = default;
|
||||
HotkeyRegistry::~HotkeyRegistry() = default;
|
||||
|
||||
void HotkeyRegistry::LoadHotkeys() {
|
||||
// Make sure NOT to use a reference here because it would become invalid once we call
|
||||
// beginGroup()
|
||||
for (auto shortcut : UISettings::values.shortcuts) {
|
||||
const QStringList cat = shortcut.first.split('/');
|
||||
Q_ASSERT(cat.size() >= 2);
|
||||
|
||||
// RegisterHotkey assigns default keybindings, so use old values as default parameters
|
||||
Hotkey& hk = hotkey_groups[cat[0]][cat[1]];
|
||||
if (!shortcut.second.first.isEmpty()) {
|
||||
hk.keyseq = QKeySequence::fromString(shortcut.second.first);
|
||||
hk.context = static_cast<Qt::ShortcutContext>(shortcut.second.second);
|
||||
}
|
||||
if (hk.shortcut)
|
||||
hk.shortcut->setKey(hk.keyseq);
|
||||
}
|
||||
}
|
||||
|
||||
void HotkeyRegistry::SaveHotkeys() {
|
||||
UISettings::values.shortcuts.clear();
|
||||
for (const auto& group : hotkey_groups) {
|
||||
for (const auto& hotkey : group.second) {
|
||||
UISettings::values.shortcuts.emplace_back(
|
||||
UISettings::Shortcut(group.first + '/' + hotkey.first,
|
||||
UISettings::ContextualShortcut(hotkey.second.keyseq.toString(),
|
||||
hotkey.second.context)));
|
||||
UISettings::values.shortcuts.push_back(
|
||||
{hotkey.first, group.first,
|
||||
UISettings::ContextualShortcut(hotkey.second.keyseq.toString(),
|
||||
hotkey.second.context)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HotkeyRegistry::RegisterHotkey(const QString& group, const QString& action,
|
||||
const QKeySequence& default_keyseq,
|
||||
Qt::ShortcutContext default_context) {
|
||||
auto& hotkey_group = hotkey_groups[group];
|
||||
if (hotkey_group.find(action) != hotkey_group.end()) {
|
||||
return;
|
||||
void HotkeyRegistry::LoadHotkeys() {
|
||||
// Make sure NOT to use a reference here because it would become invalid once we call
|
||||
// beginGroup()
|
||||
for (auto shortcut : UISettings::values.shortcuts) {
|
||||
Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name];
|
||||
if (!shortcut.shortcut.first.isEmpty()) {
|
||||
hk.keyseq = QKeySequence::fromString(shortcut.shortcut.first, QKeySequence::NativeText);
|
||||
hk.context = static_cast<Qt::ShortcutContext>(shortcut.shortcut.second);
|
||||
}
|
||||
if (hk.shortcut) {
|
||||
hk.shortcut->disconnect();
|
||||
hk.shortcut->setKey(hk.keyseq);
|
||||
}
|
||||
}
|
||||
|
||||
auto& hotkey_action = hotkey_groups[group][action];
|
||||
hotkey_action.keyseq = default_keyseq;
|
||||
hotkey_action.context = default_context;
|
||||
}
|
||||
|
||||
QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action, QWidget* widget) {
|
||||
@@ -65,24 +49,11 @@ QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action
|
||||
return hk.shortcut;
|
||||
}
|
||||
|
||||
GHotkeysDialog::GHotkeysDialog(QWidget* parent) : QWidget(parent) {
|
||||
ui.setupUi(this);
|
||||
QKeySequence HotkeyRegistry::GetKeySequence(const QString& group, const QString& action) {
|
||||
return hotkey_groups[group][action].keyseq;
|
||||
}
|
||||
|
||||
void GHotkeysDialog::Populate(const HotkeyRegistry& registry) {
|
||||
for (const auto& group : registry.hotkey_groups) {
|
||||
QTreeWidgetItem* toplevel_item = new QTreeWidgetItem(QStringList(group.first));
|
||||
for (const auto& hotkey : group.second) {
|
||||
QStringList columns;
|
||||
columns << hotkey.first << hotkey.second.keyseq.toString();
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem(columns);
|
||||
toplevel_item->addChild(item);
|
||||
}
|
||||
ui.treeWidget->addTopLevelItem(toplevel_item);
|
||||
}
|
||||
// TODO: Make context configurable as well (hiding the column for now)
|
||||
ui.treeWidget->setColumnCount(2);
|
||||
|
||||
ui.treeWidget->resizeColumnToContents(0);
|
||||
ui.treeWidget->resizeColumnToContents(1);
|
||||
Qt::ShortcutContext HotkeyRegistry::GetShortcutContext(const QString& group,
|
||||
const QString& action) {
|
||||
return hotkey_groups[group][action].context;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include "ui_hotkeys.h"
|
||||
|
||||
class QDialog;
|
||||
class QKeySequence;
|
||||
@@ -14,7 +13,7 @@ class QShortcut;
|
||||
|
||||
class HotkeyRegistry final {
|
||||
public:
|
||||
friend class GHotkeysDialog;
|
||||
friend class ConfigureHotkeys;
|
||||
|
||||
explicit HotkeyRegistry();
|
||||
~HotkeyRegistry();
|
||||
@@ -49,22 +48,27 @@ public:
|
||||
QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget);
|
||||
|
||||
/**
|
||||
* Register a hotkey.
|
||||
* Returns a QKeySequence object whose signal can be connected to QAction::setShortcut.
|
||||
*
|
||||
* @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger")
|
||||
* @param action Name of the action (e.g. "Start Emulation", "Load Image")
|
||||
* @param default_keyseq Default key sequence to assign if the hotkey wasn't present in the
|
||||
* settings file before
|
||||
* @param default_context Default context to assign if the hotkey wasn't present in the settings
|
||||
* file before
|
||||
* @warning Both the group and action strings will be displayed in the hotkey settings dialog
|
||||
* @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger").
|
||||
* @param action Name of the action (e.g. "Start Emulation", "Load Image").
|
||||
*/
|
||||
void RegisterHotkey(const QString& group, const QString& action,
|
||||
const QKeySequence& default_keyseq = {},
|
||||
Qt::ShortcutContext default_context = Qt::WindowShortcut);
|
||||
QKeySequence GetKeySequence(const QString& group, const QString& action);
|
||||
|
||||
/**
|
||||
* Returns a Qt::ShortcutContext object who can be connected to other
|
||||
* QAction::setShortcutContext.
|
||||
*
|
||||
* @param group General group this shortcut context belongs to (e.g. "Main Window",
|
||||
* "Debugger").
|
||||
* @param action Name of the action (e.g. "Start Emulation", "Load Image").
|
||||
*/
|
||||
Qt::ShortcutContext GetShortcutContext(const QString& group, const QString& action);
|
||||
|
||||
private:
|
||||
struct Hotkey {
|
||||
Hotkey() : shortcut(nullptr), context(Qt::WindowShortcut) {}
|
||||
|
||||
QKeySequence keyseq;
|
||||
QShortcut* shortcut = nullptr;
|
||||
Qt::ShortcutContext context = Qt::WindowShortcut;
|
||||
@@ -75,15 +79,3 @@ private:
|
||||
|
||||
HotkeyGroupMap hotkey_groups;
|
||||
};
|
||||
|
||||
class GHotkeysDialog : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GHotkeysDialog(QWidget* parent = nullptr);
|
||||
|
||||
void Populate(const HotkeyRegistry& registry);
|
||||
|
||||
private:
|
||||
Ui::hotkeys ui;
|
||||
};
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>hotkeys</class>
|
||||
<widget class="QWidget" name="hotkeys">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>363</width>
|
||||
<height>388</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Hotkey Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="treeWidget">
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectItems</enum>
|
||||
</property>
|
||||
<property name="headerHidden">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Action</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Hotkey</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Context</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -511,33 +511,34 @@ void GMainWindow::InitializeRecentFileMenuActions() {
|
||||
}
|
||||
|
||||
void GMainWindow::InitializeHotkeys() {
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Load File", QKeySequence::Open);
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Start Emulation");
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Continue/Pause", QKeySequence(Qt::Key_F4));
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Restart", QKeySequence(Qt::Key_F5));
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Fullscreen", QKeySequence::FullScreen);
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence(Qt::Key_Escape),
|
||||
Qt::ApplicationShortcut);
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Toggle Speed Limit", QKeySequence("CTRL+Z"),
|
||||
Qt::ApplicationShortcut);
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Increase Speed Limit", QKeySequence("+"),
|
||||
Qt::ApplicationShortcut);
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Decrease Speed Limit", QKeySequence("-"),
|
||||
Qt::ApplicationShortcut);
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Load Amiibo", QKeySequence(Qt::Key_F2),
|
||||
Qt::ApplicationShortcut);
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Capture Screenshot",
|
||||
QKeySequence(QKeySequence::Print));
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Change Docked Mode", QKeySequence(Qt::Key_F10));
|
||||
|
||||
hotkey_registry.LoadHotkeys();
|
||||
|
||||
ui.action_Load_File->setShortcut(hotkey_registry.GetKeySequence("Main Window", "Load File"));
|
||||
ui.action_Load_File->setShortcutContext(
|
||||
hotkey_registry.GetShortcutContext("Main Window", "Load File"));
|
||||
|
||||
ui.action_Exit->setShortcut(hotkey_registry.GetKeySequence("Main Window", "Exit yuzu"));
|
||||
ui.action_Exit->setShortcutContext(
|
||||
hotkey_registry.GetShortcutContext("Main Window", "Exit yuzu"));
|
||||
|
||||
ui.action_Stop->setShortcut(hotkey_registry.GetKeySequence("Main Window", "Stop Emulation"));
|
||||
ui.action_Stop->setShortcutContext(
|
||||
hotkey_registry.GetShortcutContext("Main Window", "Stop Emulation"));
|
||||
|
||||
ui.action_Show_Filter_Bar->setShortcut(
|
||||
hotkey_registry.GetKeySequence("Main Window", "Toggle Filter Bar"));
|
||||
ui.action_Show_Filter_Bar->setShortcutContext(
|
||||
hotkey_registry.GetShortcutContext("Main Window", "Toggle Filter Bar"));
|
||||
|
||||
ui.action_Show_Status_Bar->setShortcut(
|
||||
hotkey_registry.GetKeySequence("Main Window", "Toggle Status Bar"));
|
||||
ui.action_Show_Status_Bar->setShortcutContext(
|
||||
hotkey_registry.GetShortcutContext("Main Window", "Toggle Status Bar"));
|
||||
|
||||
connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated,
|
||||
this, &GMainWindow::OnMenuLoadFile);
|
||||
connect(hotkey_registry.GetHotkey("Main Window", "Start Emulation", this),
|
||||
&QShortcut::activated, this, &GMainWindow::OnStartGame);
|
||||
connect(hotkey_registry.GetHotkey("Main Window", "Continue/Pause", this), &QShortcut::activated,
|
||||
this, [&] {
|
||||
connect(hotkey_registry.GetHotkey("Main Window", "Continue/Pause Emulation", this),
|
||||
&QShortcut::activated, this, [&] {
|
||||
if (emulation_running) {
|
||||
if (emu_thread->IsRunning()) {
|
||||
OnPauseGame();
|
||||
@@ -546,8 +547,8 @@ void GMainWindow::InitializeHotkeys() {
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(hotkey_registry.GetHotkey("Main Window", "Restart", this), &QShortcut::activated, this,
|
||||
[this] {
|
||||
connect(hotkey_registry.GetHotkey("Main Window", "Restart Emulation", this),
|
||||
&QShortcut::activated, this, [this] {
|
||||
if (!Core::System::GetInstance().IsPoweredOn())
|
||||
return;
|
||||
BootGame(QString(game_path));
|
||||
@@ -692,7 +693,6 @@ void GMainWindow::ConnectMenuEvents() {
|
||||
&GMainWindow::ToggleWindowMode);
|
||||
connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this,
|
||||
&GMainWindow::OnDisplayTitleBars);
|
||||
ui.action_Show_Filter_Bar->setShortcut(tr("CTRL+F"));
|
||||
connect(ui.action_Show_Filter_Bar, &QAction::triggered, this, &GMainWindow::OnToggleFilterBar);
|
||||
connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible);
|
||||
|
||||
@@ -1624,6 +1624,7 @@ void GMainWindow::OnConfigure() {
|
||||
auto result = configureDialog.exec();
|
||||
if (result == QDialog::Accepted) {
|
||||
configureDialog.applyConfiguration();
|
||||
InitializeHotkeys();
|
||||
if (UISettings::values.theme != old_theme)
|
||||
UpdateUITheme();
|
||||
if (UISettings::values.enable_discord_presence != old_discord_presence)
|
||||
|
||||
@@ -120,7 +120,6 @@ private:
|
||||
void InitializeWidgets();
|
||||
void InitializeDebugWidgets();
|
||||
void InitializeRecentFileMenuActions();
|
||||
void InitializeHotkeys();
|
||||
|
||||
void SetDefaultUIGeometry();
|
||||
void RestoreUIState();
|
||||
@@ -195,6 +194,7 @@ private slots:
|
||||
void OnAbout();
|
||||
void OnToggleFilterBar();
|
||||
void OnDisplayTitleBars(bool);
|
||||
void InitializeHotkeys();
|
||||
void ToggleFullscreen();
|
||||
void ShowFullscreen();
|
||||
void HideFullscreen();
|
||||
|
||||
@@ -12,5 +12,4 @@ const Themes themes{{
|
||||
}};
|
||||
|
||||
Values values = {};
|
||||
|
||||
} // namespace UISettings
|
||||
|
||||
@@ -15,7 +15,12 @@
|
||||
namespace UISettings {
|
||||
|
||||
using ContextualShortcut = std::pair<QString, int>;
|
||||
using Shortcut = std::pair<QString, ContextualShortcut>;
|
||||
|
||||
struct Shortcut {
|
||||
QString name;
|
||||
QString group;
|
||||
ContextualShortcut shortcut;
|
||||
};
|
||||
|
||||
using Themes = std::array<std::pair<const char*, const char*>, 2>;
|
||||
extern const Themes themes;
|
||||
|
||||
37
src/yuzu/util/sequence_dialog/sequence_dialog.cpp
Normal file
37
src/yuzu/util/sequence_dialog/sequence_dialog.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QKeySequenceEdit>
|
||||
#include <QVBoxLayout>
|
||||
#include "yuzu/util/sequence_dialog/sequence_dialog.h"
|
||||
|
||||
SequenceDialog::SequenceDialog(QWidget* parent) : QDialog(parent) {
|
||||
setWindowTitle(tr("Enter a hotkey"));
|
||||
auto* layout = new QVBoxLayout(this);
|
||||
key_sequence = new QKeySequenceEdit;
|
||||
layout->addWidget(key_sequence);
|
||||
auto* buttons =
|
||||
new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal);
|
||||
buttons->setCenterButtons(true);
|
||||
layout->addWidget(buttons);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
}
|
||||
|
||||
SequenceDialog::~SequenceDialog() = default;
|
||||
|
||||
QKeySequence SequenceDialog::GetSequence() const {
|
||||
// Only the first key is returned. The other 3, if present, are ignored.
|
||||
return QKeySequence(key_sequence->keySequence()[0]);
|
||||
}
|
||||
|
||||
bool SequenceDialog::focusNextPrevChild(bool next) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void SequenceDialog::closeEvent(QCloseEvent*) {
|
||||
reject();
|
||||
}
|
||||
24
src/yuzu/util/sequence_dialog/sequence_dialog.h
Normal file
24
src/yuzu/util/sequence_dialog/sequence_dialog.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class QKeySequenceEdit;
|
||||
|
||||
class SequenceDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SequenceDialog(QWidget* parent = nullptr);
|
||||
~SequenceDialog() override;
|
||||
|
||||
QKeySequence GetSequence() const;
|
||||
void closeEvent(QCloseEvent*) override;
|
||||
|
||||
private:
|
||||
QKeySequenceEdit* key_sequence;
|
||||
bool focusNextPrevChild(bool next) override;
|
||||
};
|
||||
@@ -346,7 +346,7 @@ void Config::ReadValues() {
|
||||
|
||||
// Renderer
|
||||
Settings::values.resolution_factor =
|
||||
(float)sdl2_config->GetReal("Renderer", "resolution_factor", 1.0);
|
||||
static_cast<float>(sdl2_config->GetReal("Renderer", "resolution_factor", 1.0));
|
||||
Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true);
|
||||
Settings::values.frame_limit =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100));
|
||||
@@ -357,16 +357,17 @@ void Config::ReadValues() {
|
||||
Settings::values.use_asynchronous_gpu_emulation =
|
||||
sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false);
|
||||
|
||||
Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 0.0);
|
||||
Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 0.0);
|
||||
Settings::values.bg_blue = (float)sdl2_config->GetReal("Renderer", "bg_blue", 0.0);
|
||||
Settings::values.bg_red = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0));
|
||||
Settings::values.bg_green =
|
||||
static_cast<float>(sdl2_config->GetReal("Renderer", "bg_green", 0.0));
|
||||
Settings::values.bg_blue = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_blue", 0.0));
|
||||
|
||||
// Audio
|
||||
Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto");
|
||||
Settings::values.enable_audio_stretching =
|
||||
sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
|
||||
Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto");
|
||||
Settings::values.volume = sdl2_config->GetReal("Audio", "volume", 1);
|
||||
Settings::values.volume = static_cast<float>(sdl2_config->GetReal("Audio", "volume", 1));
|
||||
|
||||
Settings::values.language_index = sdl2_config->GetInteger("System", "language_index", 1);
|
||||
|
||||
|
||||
@@ -135,16 +135,16 @@ bool EmuWindow_SDL2::SupportsRequiredGLExtensions() {
|
||||
}
|
||||
|
||||
EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
||||
InputCommon::Init();
|
||||
|
||||
SDL_SetMainReady();
|
||||
|
||||
// Initialize the window
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) {
|
||||
LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting...");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
InputCommon::Init();
|
||||
|
||||
SDL_SetMainReady();
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
@@ -201,11 +201,9 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
||||
}
|
||||
|
||||
EmuWindow_SDL2::~EmuWindow_SDL2() {
|
||||
InputCommon::SDL::CloseSDLJoysticks();
|
||||
InputCommon::Shutdown();
|
||||
SDL_GL_DeleteContext(gl_context);
|
||||
SDL_Quit();
|
||||
|
||||
InputCommon::Shutdown();
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::SwapBuffers() {
|
||||
@@ -262,7 +260,6 @@ void EmuWindow_SDL2::PollEvents() {
|
||||
is_open = false;
|
||||
break;
|
||||
default:
|
||||
InputCommon::SDL::HandleGameControllerEvent(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user