diff options
Diffstat (limited to 'src')
42 files changed, 1007 insertions, 211 deletions
diff --git a/src/common/x64/cpu_detect.cpp b/src/common/x64/cpu_detect.cpp index 1a27532d4..e54383a4a 100644 --- a/src/common/x64/cpu_detect.cpp +++ b/src/common/x64/cpu_detect.cpp | |||
| @@ -4,14 +4,27 @@ | |||
| 4 | 4 | ||
| 5 | #include <array> | 5 | #include <array> |
| 6 | #include <cstring> | 6 | #include <cstring> |
| 7 | #include <fstream> | ||
| 7 | #include <iterator> | 8 | #include <iterator> |
| 9 | #include <optional> | ||
| 8 | #include <string_view> | 10 | #include <string_view> |
| 11 | #include <thread> | ||
| 12 | #include <vector> | ||
| 9 | #include "common/bit_util.h" | 13 | #include "common/bit_util.h" |
| 10 | #include "common/common_types.h" | 14 | #include "common/common_types.h" |
| 15 | #include "common/logging/log.h" | ||
| 11 | #include "common/x64/cpu_detect.h" | 16 | #include "common/x64/cpu_detect.h" |
| 12 | 17 | ||
| 18 | #ifdef _WIN32 | ||
| 19 | #include <windows.h> | ||
| 20 | #endif | ||
| 21 | |||
| 13 | #ifdef _MSC_VER | 22 | #ifdef _MSC_VER |
| 14 | #include <intrin.h> | 23 | #include <intrin.h> |
| 24 | |||
| 25 | static inline u64 xgetbv(u32 index) { | ||
| 26 | return _xgetbv(index); | ||
| 27 | } | ||
| 15 | #else | 28 | #else |
| 16 | 29 | ||
| 17 | #if defined(__DragonFly__) || defined(__FreeBSD__) | 30 | #if defined(__DragonFly__) || defined(__FreeBSD__) |
| @@ -39,12 +52,11 @@ static inline void __cpuid(int info[4], u32 function_id) { | |||
| 39 | } | 52 | } |
| 40 | 53 | ||
| 41 | #define _XCR_XFEATURE_ENABLED_MASK 0 | 54 | #define _XCR_XFEATURE_ENABLED_MASK 0 |
| 42 | static inline u64 _xgetbv(u32 index) { | 55 | static inline u64 xgetbv(u32 index) { |
| 43 | u32 eax, edx; | 56 | u32 eax, edx; |
| 44 | __asm__ __volatile__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(index)); | 57 | __asm__ __volatile__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(index)); |
| 45 | return ((u64)edx << 32) | eax; | 58 | return ((u64)edx << 32) | eax; |
| 46 | } | 59 | } |
| 47 | |||
| 48 | #endif // _MSC_VER | 60 | #endif // _MSC_VER |
| 49 | 61 | ||
| 50 | namespace Common { | 62 | namespace Common { |
| @@ -107,7 +119,7 @@ static CPUCaps Detect() { | |||
| 107 | // - Is the XSAVE bit set in CPUID? | 119 | // - Is the XSAVE bit set in CPUID? |
| 108 | // - XGETBV result has the XCR bit set. | 120 | // - XGETBV result has the XCR bit set. |
| 109 | if (Common::Bit<28>(cpu_id[2]) && Common::Bit<27>(cpu_id[2])) { | 121 | if (Common::Bit<28>(cpu_id[2]) && Common::Bit<27>(cpu_id[2])) { |
| 110 | if ((_xgetbv(_XCR_XFEATURE_ENABLED_MASK) & 0x6) == 0x6) { | 122 | if ((xgetbv(_XCR_XFEATURE_ENABLED_MASK) & 0x6) == 0x6) { |
| 111 | caps.avx = true; | 123 | caps.avx = true; |
| 112 | if (Common::Bit<12>(cpu_id[2])) | 124 | if (Common::Bit<12>(cpu_id[2])) |
| 113 | caps.fma = true; | 125 | caps.fma = true; |
| @@ -192,4 +204,45 @@ const CPUCaps& GetCPUCaps() { | |||
| 192 | return caps; | 204 | return caps; |
| 193 | } | 205 | } |
| 194 | 206 | ||
| 207 | std::optional<int> GetProcessorCount() { | ||
| 208 | #if defined(_WIN32) | ||
| 209 | // Get the buffer length. | ||
| 210 | DWORD length = 0; | ||
| 211 | GetLogicalProcessorInformation(nullptr, &length); | ||
| 212 | if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { | ||
| 213 | LOG_ERROR(Frontend, "Failed to query core count."); | ||
| 214 | return std::nullopt; | ||
| 215 | } | ||
| 216 | std::vector<SYSTEM_LOGICAL_PROCESSOR_INFORMATION> buffer( | ||
| 217 | length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)); | ||
| 218 | // Now query the core count. | ||
| 219 | if (!GetLogicalProcessorInformation(buffer.data(), &length)) { | ||
| 220 | LOG_ERROR(Frontend, "Failed to query core count."); | ||
| 221 | return std::nullopt; | ||
| 222 | } | ||
| 223 | return static_cast<int>( | ||
| 224 | std::count_if(buffer.cbegin(), buffer.cend(), [](const auto& proc_info) { | ||
| 225 | return proc_info.Relationship == RelationProcessorCore; | ||
| 226 | })); | ||
| 227 | #elif defined(__unix__) | ||
| 228 | const int thread_count = std::thread::hardware_concurrency(); | ||
| 229 | std::ifstream smt("/sys/devices/system/cpu/smt/active"); | ||
| 230 | char state = '0'; | ||
| 231 | if (smt) { | ||
| 232 | smt.read(&state, sizeof(state)); | ||
| 233 | } | ||
| 234 | switch (state) { | ||
| 235 | case '0': | ||
| 236 | return thread_count; | ||
| 237 | case '1': | ||
| 238 | return thread_count / 2; | ||
| 239 | default: | ||
| 240 | return std::nullopt; | ||
| 241 | } | ||
| 242 | #else | ||
| 243 | // Shame on you | ||
| 244 | return std::nullopt; | ||
| 245 | #endif | ||
| 246 | } | ||
| 247 | |||
| 195 | } // namespace Common | 248 | } // namespace Common |
diff --git a/src/common/x64/cpu_detect.h b/src/common/x64/cpu_detect.h index 6830f3795..ca8db19d6 100644 --- a/src/common/x64/cpu_detect.h +++ b/src/common/x64/cpu_detect.h | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <optional> | ||
| 7 | #include <string_view> | 8 | #include <string_view> |
| 8 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 9 | 10 | ||
| @@ -74,4 +75,7 @@ struct CPUCaps { | |||
| 74 | */ | 75 | */ |
| 75 | const CPUCaps& GetCPUCaps(); | 76 | const CPUCaps& GetCPUCaps(); |
| 76 | 77 | ||
| 78 | /// Detects CPU core count | ||
| 79 | std::optional<int> GetProcessorCount(); | ||
| 80 | |||
| 77 | } // namespace Common | 81 | } // namespace Common |
diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp index 339f971e6..1a8e02e6a 100644 --- a/src/core/debugger/debugger.cpp +++ b/src/core/debugger/debugger.cpp | |||
| @@ -27,12 +27,21 @@ static void AsyncReceiveInto(Readable& r, Buffer& buffer, Callback&& c) { | |||
| 27 | const u8* buffer_start = reinterpret_cast<const u8*>(&buffer); | 27 | const u8* buffer_start = reinterpret_cast<const u8*>(&buffer); |
| 28 | std::span<const u8> received_data{buffer_start, buffer_start + bytes_read}; | 28 | std::span<const u8> received_data{buffer_start, buffer_start + bytes_read}; |
| 29 | c(received_data); | 29 | c(received_data); |
| 30 | AsyncReceiveInto(r, buffer, c); | ||
| 30 | } | 31 | } |
| 31 | |||
| 32 | AsyncReceiveInto(r, buffer, c); | ||
| 33 | }); | 32 | }); |
| 34 | } | 33 | } |
| 35 | 34 | ||
| 35 | template <typename Callback> | ||
| 36 | static void AsyncAccept(boost::asio::ip::tcp::acceptor& acceptor, Callback&& c) { | ||
| 37 | acceptor.async_accept([&, c](const boost::system::error_code& error, auto&& peer_socket) { | ||
| 38 | if (!error.failed()) { | ||
| 39 | c(peer_socket); | ||
| 40 | AsyncAccept(acceptor, c); | ||
| 41 | } | ||
| 42 | }); | ||
| 43 | } | ||
| 44 | |||
| 36 | template <typename Readable, typename Buffer> | 45 | template <typename Readable, typename Buffer> |
| 37 | static std::span<const u8> ReceiveInto(Readable& r, Buffer& buffer) { | 46 | static std::span<const u8> ReceiveInto(Readable& r, Buffer& buffer) { |
| 38 | static_assert(std::is_trivial_v<Buffer>); | 47 | static_assert(std::is_trivial_v<Buffer>); |
| @@ -59,9 +68,7 @@ namespace Core { | |||
| 59 | 68 | ||
| 60 | class DebuggerImpl : public DebuggerBackend { | 69 | class DebuggerImpl : public DebuggerBackend { |
| 61 | public: | 70 | public: |
| 62 | explicit DebuggerImpl(Core::System& system_, u16 port) | 71 | explicit DebuggerImpl(Core::System& system_, u16 port) : system{system_} { |
| 63 | : system{system_}, signal_pipe{io_context}, client_socket{io_context} { | ||
| 64 | frontend = std::make_unique<GDBStub>(*this, system); | ||
| 65 | InitializeServer(port); | 72 | InitializeServer(port); |
| 66 | } | 73 | } |
| 67 | 74 | ||
| @@ -70,39 +77,42 @@ public: | |||
| 70 | } | 77 | } |
| 71 | 78 | ||
| 72 | bool SignalDebugger(SignalInfo signal_info) { | 79 | bool SignalDebugger(SignalInfo signal_info) { |
| 73 | { | 80 | std::scoped_lock lk{connection_lock}; |
| 74 | std::scoped_lock lk{connection_lock}; | ||
| 75 | 81 | ||
| 76 | if (stopped) { | 82 | if (stopped || !state) { |
| 77 | // Do not notify the debugger about another event. | 83 | // Do not notify the debugger about another event. |
| 78 | // It should be ignored. | 84 | // It should be ignored. |
| 79 | return false; | 85 | return false; |
| 80 | } | ||
| 81 | |||
| 82 | // Set up the state. | ||
| 83 | stopped = true; | ||
| 84 | info = signal_info; | ||
| 85 | } | 86 | } |
| 86 | 87 | ||
| 88 | // Set up the state. | ||
| 89 | stopped = true; | ||
| 90 | state->info = signal_info; | ||
| 91 | |||
| 87 | // Write a single byte into the pipe to wake up the debug interface. | 92 | // Write a single byte into the pipe to wake up the debug interface. |
| 88 | boost::asio::write(signal_pipe, boost::asio::buffer(&stopped, sizeof(stopped))); | 93 | boost::asio::write(state->signal_pipe, boost::asio::buffer(&stopped, sizeof(stopped))); |
| 94 | |||
| 89 | return true; | 95 | return true; |
| 90 | } | 96 | } |
| 91 | 97 | ||
| 98 | // These functions are callbacks from the frontend, and the lock will be held. | ||
| 99 | // There is no need to relock it. | ||
| 100 | |||
| 92 | std::span<const u8> ReadFromClient() override { | 101 | std::span<const u8> ReadFromClient() override { |
| 93 | return ReceiveInto(client_socket, client_data); | 102 | return ReceiveInto(state->client_socket, state->client_data); |
| 94 | } | 103 | } |
| 95 | 104 | ||
| 96 | void WriteToClient(std::span<const u8> data) override { | 105 | void WriteToClient(std::span<const u8> data) override { |
| 97 | boost::asio::write(client_socket, boost::asio::buffer(data.data(), data.size_bytes())); | 106 | boost::asio::write(state->client_socket, |
| 107 | boost::asio::buffer(data.data(), data.size_bytes())); | ||
| 98 | } | 108 | } |
| 99 | 109 | ||
| 100 | void SetActiveThread(Kernel::KThread* thread) override { | 110 | void SetActiveThread(Kernel::KThread* thread) override { |
| 101 | active_thread = thread; | 111 | state->active_thread = thread; |
| 102 | } | 112 | } |
| 103 | 113 | ||
| 104 | Kernel::KThread* GetActiveThread() override { | 114 | Kernel::KThread* GetActiveThread() override { |
| 105 | return active_thread; | 115 | return state->active_thread; |
| 106 | } | 116 | } |
| 107 | 117 | ||
| 108 | private: | 118 | private: |
| @@ -113,65 +123,78 @@ private: | |||
| 113 | 123 | ||
| 114 | // Run the connection thread. | 124 | // Run the connection thread. |
| 115 | connection_thread = std::jthread([&, port](std::stop_token stop_token) { | 125 | connection_thread = std::jthread([&, port](std::stop_token stop_token) { |
| 126 | Common::SetCurrentThreadName("Debugger"); | ||
| 127 | |||
| 116 | try { | 128 | try { |
| 117 | // Initialize the listening socket and accept a new client. | 129 | // Initialize the listening socket and accept a new client. |
| 118 | tcp::endpoint endpoint{boost::asio::ip::address_v4::any(), port}; | 130 | tcp::endpoint endpoint{boost::asio::ip::address_v4::any(), port}; |
| 119 | tcp::acceptor acceptor{io_context, endpoint}; | 131 | tcp::acceptor acceptor{io_context, endpoint}; |
| 120 | 132 | ||
| 121 | acceptor.async_accept(client_socket, [](const auto&) {}); | 133 | AsyncAccept(acceptor, [&](auto&& peer) { AcceptConnection(std::move(peer)); }); |
| 122 | io_context.run_one(); | ||
| 123 | io_context.restart(); | ||
| 124 | 134 | ||
| 125 | if (stop_token.stop_requested()) { | 135 | while (!stop_token.stop_requested() && io_context.run()) { |
| 126 | return; | ||
| 127 | } | 136 | } |
| 128 | |||
| 129 | ThreadLoop(stop_token); | ||
| 130 | } catch (const std::exception& ex) { | 137 | } catch (const std::exception& ex) { |
| 131 | LOG_CRITICAL(Debug_GDBStub, "Stopping server: {}", ex.what()); | 138 | LOG_CRITICAL(Debug_GDBStub, "Stopping server: {}", ex.what()); |
| 132 | } | 139 | } |
| 133 | }); | 140 | }); |
| 134 | } | 141 | } |
| 135 | 142 | ||
| 136 | void ShutdownServer() { | 143 | void AcceptConnection(boost::asio::ip::tcp::socket&& peer) { |
| 137 | connection_thread.request_stop(); | 144 | LOG_INFO(Debug_GDBStub, "Accepting new peer connection"); |
| 138 | io_context.stop(); | ||
| 139 | connection_thread.join(); | ||
| 140 | } | ||
| 141 | 145 | ||
| 142 | void ThreadLoop(std::stop_token stop_token) { | 146 | std::scoped_lock lk{connection_lock}; |
| 143 | Common::SetCurrentThreadName("Debugger"); | 147 | |
| 148 | // Ensure everything is stopped. | ||
| 149 | PauseEmulation(); | ||
| 150 | |||
| 151 | // Set up the new frontend. | ||
| 152 | frontend = std::make_unique<GDBStub>(*this, system); | ||
| 153 | |||
| 154 | // Set the new state. This will tear down any existing state. | ||
| 155 | state = ConnectionState{ | ||
| 156 | .client_socket{std::move(peer)}, | ||
| 157 | .signal_pipe{io_context}, | ||
| 158 | .info{}, | ||
| 159 | .active_thread{}, | ||
| 160 | .client_data{}, | ||
| 161 | .pipe_data{}, | ||
| 162 | }; | ||
| 144 | 163 | ||
| 145 | // Set up the client signals for new data. | 164 | // Set up the client signals for new data. |
| 146 | AsyncReceiveInto(signal_pipe, pipe_data, [&](auto d) { PipeData(d); }); | 165 | AsyncReceiveInto(state->signal_pipe, state->pipe_data, [&](auto d) { PipeData(d); }); |
| 147 | AsyncReceiveInto(client_socket, client_data, [&](auto d) { ClientData(d); }); | 166 | AsyncReceiveInto(state->client_socket, state->client_data, [&](auto d) { ClientData(d); }); |
| 148 | 167 | ||
| 149 | // Set the active thread. | 168 | // Set the active thread. |
| 150 | UpdateActiveThread(); | 169 | UpdateActiveThread(); |
| 151 | 170 | ||
| 152 | // Set up the frontend. | 171 | // Set up the frontend. |
| 153 | frontend->Connected(); | 172 | frontend->Connected(); |
| 173 | } | ||
| 154 | 174 | ||
| 155 | // Main event loop. | 175 | void ShutdownServer() { |
| 156 | while (!stop_token.stop_requested() && io_context.run()) { | 176 | connection_thread.request_stop(); |
| 157 | } | 177 | io_context.stop(); |
| 178 | connection_thread.join(); | ||
| 158 | } | 179 | } |
| 159 | 180 | ||
| 160 | void PipeData(std::span<const u8> data) { | 181 | void PipeData(std::span<const u8> data) { |
| 161 | switch (info.type) { | 182 | std::scoped_lock lk{connection_lock}; |
| 183 | |||
| 184 | switch (state->info.type) { | ||
| 162 | case SignalType::Stopped: | 185 | case SignalType::Stopped: |
| 163 | case SignalType::Watchpoint: | 186 | case SignalType::Watchpoint: |
| 164 | // Stop emulation. | 187 | // Stop emulation. |
| 165 | PauseEmulation(); | 188 | PauseEmulation(); |
| 166 | 189 | ||
| 167 | // Notify the client. | 190 | // Notify the client. |
| 168 | active_thread = info.thread; | 191 | state->active_thread = state->info.thread; |
| 169 | UpdateActiveThread(); | 192 | UpdateActiveThread(); |
| 170 | 193 | ||
| 171 | if (info.type == SignalType::Watchpoint) { | 194 | if (state->info.type == SignalType::Watchpoint) { |
| 172 | frontend->Watchpoint(active_thread, *info.watchpoint); | 195 | frontend->Watchpoint(state->active_thread, *state->info.watchpoint); |
| 173 | } else { | 196 | } else { |
| 174 | frontend->Stopped(active_thread); | 197 | frontend->Stopped(state->active_thread); |
| 175 | } | 198 | } |
| 176 | 199 | ||
| 177 | break; | 200 | break; |
| @@ -179,8 +202,8 @@ private: | |||
| 179 | frontend->ShuttingDown(); | 202 | frontend->ShuttingDown(); |
| 180 | 203 | ||
| 181 | // Wait for emulation to shut down gracefully now. | 204 | // Wait for emulation to shut down gracefully now. |
| 182 | signal_pipe.close(); | 205 | state->signal_pipe.close(); |
| 183 | client_socket.shutdown(boost::asio::socket_base::shutdown_both); | 206 | state->client_socket.shutdown(boost::asio::socket_base::shutdown_both); |
| 184 | LOG_INFO(Debug_GDBStub, "Shut down server"); | 207 | LOG_INFO(Debug_GDBStub, "Shut down server"); |
| 185 | 208 | ||
| 186 | break; | 209 | break; |
| @@ -188,17 +211,16 @@ private: | |||
| 188 | } | 211 | } |
| 189 | 212 | ||
| 190 | void ClientData(std::span<const u8> data) { | 213 | void ClientData(std::span<const u8> data) { |
| 214 | std::scoped_lock lk{connection_lock}; | ||
| 215 | |||
| 191 | const auto actions{frontend->ClientData(data)}; | 216 | const auto actions{frontend->ClientData(data)}; |
| 192 | for (const auto action : actions) { | 217 | for (const auto action : actions) { |
| 193 | switch (action) { | 218 | switch (action) { |
| 194 | case DebuggerAction::Interrupt: { | 219 | case DebuggerAction::Interrupt: { |
| 195 | { | 220 | stopped = true; |
| 196 | std::scoped_lock lk{connection_lock}; | ||
| 197 | stopped = true; | ||
| 198 | } | ||
| 199 | PauseEmulation(); | 221 | PauseEmulation(); |
| 200 | UpdateActiveThread(); | 222 | UpdateActiveThread(); |
| 201 | frontend->Stopped(active_thread); | 223 | frontend->Stopped(state->active_thread); |
| 202 | break; | 224 | break; |
| 203 | } | 225 | } |
| 204 | case DebuggerAction::Continue: | 226 | case DebuggerAction::Continue: |
| @@ -206,15 +228,15 @@ private: | |||
| 206 | break; | 228 | break; |
| 207 | case DebuggerAction::StepThreadUnlocked: | 229 | case DebuggerAction::StepThreadUnlocked: |
| 208 | MarkResumed([&] { | 230 | MarkResumed([&] { |
| 209 | active_thread->SetStepState(Kernel::StepState::StepPending); | 231 | state->active_thread->SetStepState(Kernel::StepState::StepPending); |
| 210 | active_thread->Resume(Kernel::SuspendType::Debug); | 232 | state->active_thread->Resume(Kernel::SuspendType::Debug); |
| 211 | ResumeEmulation(active_thread); | 233 | ResumeEmulation(state->active_thread); |
| 212 | }); | 234 | }); |
| 213 | break; | 235 | break; |
| 214 | case DebuggerAction::StepThreadLocked: { | 236 | case DebuggerAction::StepThreadLocked: { |
| 215 | MarkResumed([&] { | 237 | MarkResumed([&] { |
| 216 | active_thread->SetStepState(Kernel::StepState::StepPending); | 238 | state->active_thread->SetStepState(Kernel::StepState::StepPending); |
| 217 | active_thread->Resume(Kernel::SuspendType::Debug); | 239 | state->active_thread->Resume(Kernel::SuspendType::Debug); |
| 218 | }); | 240 | }); |
| 219 | break; | 241 | break; |
| 220 | } | 242 | } |
| @@ -254,15 +276,14 @@ private: | |||
| 254 | template <typename Callback> | 276 | template <typename Callback> |
| 255 | void MarkResumed(Callback&& cb) { | 277 | void MarkResumed(Callback&& cb) { |
| 256 | Kernel::KScopedSchedulerLock sl{system.Kernel()}; | 278 | Kernel::KScopedSchedulerLock sl{system.Kernel()}; |
| 257 | std::scoped_lock cl{connection_lock}; | ||
| 258 | stopped = false; | 279 | stopped = false; |
| 259 | cb(); | 280 | cb(); |
| 260 | } | 281 | } |
| 261 | 282 | ||
| 262 | void UpdateActiveThread() { | 283 | void UpdateActiveThread() { |
| 263 | const auto& threads{ThreadList()}; | 284 | const auto& threads{ThreadList()}; |
| 264 | if (std::find(threads.begin(), threads.end(), active_thread) == threads.end()) { | 285 | if (std::find(threads.begin(), threads.end(), state->active_thread) == threads.end()) { |
| 265 | active_thread = threads[0]; | 286 | state->active_thread = threads[0]; |
| 266 | } | 287 | } |
| 267 | } | 288 | } |
| 268 | 289 | ||
| @@ -274,18 +295,22 @@ private: | |||
| 274 | System& system; | 295 | System& system; |
| 275 | std::unique_ptr<DebuggerFrontend> frontend; | 296 | std::unique_ptr<DebuggerFrontend> frontend; |
| 276 | 297 | ||
| 298 | boost::asio::io_context io_context; | ||
| 277 | std::jthread connection_thread; | 299 | std::jthread connection_thread; |
| 278 | std::mutex connection_lock; | 300 | std::mutex connection_lock; |
| 279 | boost::asio::io_context io_context; | ||
| 280 | boost::process::async_pipe signal_pipe; | ||
| 281 | boost::asio::ip::tcp::socket client_socket; | ||
| 282 | 301 | ||
| 283 | SignalInfo info; | 302 | struct ConnectionState { |
| 284 | Kernel::KThread* active_thread; | 303 | boost::asio::ip::tcp::socket client_socket; |
| 285 | bool pipe_data; | 304 | boost::process::async_pipe signal_pipe; |
| 286 | bool stopped; | 305 | |
| 306 | SignalInfo info; | ||
| 307 | Kernel::KThread* active_thread; | ||
| 308 | std::array<u8, 4096> client_data; | ||
| 309 | bool pipe_data; | ||
| 310 | }; | ||
| 287 | 311 | ||
| 288 | std::array<u8, 4096> client_data; | 312 | std::optional<ConnectionState> state{}; |
| 313 | bool stopped{}; | ||
| 289 | }; | 314 | }; |
| 290 | 315 | ||
| 291 | Debugger::Debugger(Core::System& system, u16 port) { | 316 | Debugger::Debugger(Core::System& system, u16 port) { |
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp index 884229c77..a64a9ac64 100644 --- a/src/core/debugger/gdbstub.cpp +++ b/src/core/debugger/gdbstub.cpp | |||
| @@ -606,6 +606,8 @@ void GDBStub::HandleQuery(std::string_view command) { | |||
| 606 | } else if (command.starts_with("StartNoAckMode")) { | 606 | } else if (command.starts_with("StartNoAckMode")) { |
| 607 | no_ack = true; | 607 | no_ack = true; |
| 608 | SendReply(GDB_STUB_REPLY_OK); | 608 | SendReply(GDB_STUB_REPLY_OK); |
| 609 | } else if (command.starts_with("Rcmd,")) { | ||
| 610 | HandleRcmd(Common::HexStringToVector(command.substr(5), false)); | ||
| 609 | } else { | 611 | } else { |
| 610 | SendReply(GDB_STUB_REPLY_EMPTY); | 612 | SendReply(GDB_STUB_REPLY_EMPTY); |
| 611 | } | 613 | } |
| @@ -645,6 +647,155 @@ void GDBStub::HandleVCont(std::string_view command, std::vector<DebuggerAction>& | |||
| 645 | } | 647 | } |
| 646 | } | 648 | } |
| 647 | 649 | ||
| 650 | constexpr std::array<std::pair<const char*, Kernel::Svc::MemoryState>, 22> MemoryStateNames{{ | ||
| 651 | {"----- Free -----", Kernel::Svc::MemoryState::Free}, | ||
| 652 | {"Io ", Kernel::Svc::MemoryState::Io}, | ||
| 653 | {"Static ", Kernel::Svc::MemoryState::Static}, | ||
| 654 | {"Code ", Kernel::Svc::MemoryState::Code}, | ||
| 655 | {"CodeData ", Kernel::Svc::MemoryState::CodeData}, | ||
| 656 | {"Normal ", Kernel::Svc::MemoryState::Normal}, | ||
| 657 | {"Shared ", Kernel::Svc::MemoryState::Shared}, | ||
| 658 | {"AliasCode ", Kernel::Svc::MemoryState::AliasCode}, | ||
| 659 | {"AliasCodeData ", Kernel::Svc::MemoryState::AliasCodeData}, | ||
| 660 | {"Ipc ", Kernel::Svc::MemoryState::Ipc}, | ||
| 661 | {"Stack ", Kernel::Svc::MemoryState::Stack}, | ||
| 662 | {"ThreadLocal ", Kernel::Svc::MemoryState::ThreadLocal}, | ||
| 663 | {"Transfered ", Kernel::Svc::MemoryState::Transfered}, | ||
| 664 | {"SharedTransfered", Kernel::Svc::MemoryState::SharedTransfered}, | ||
| 665 | {"SharedCode ", Kernel::Svc::MemoryState::SharedCode}, | ||
| 666 | {"Inaccessible ", Kernel::Svc::MemoryState::Inaccessible}, | ||
| 667 | {"NonSecureIpc ", Kernel::Svc::MemoryState::NonSecureIpc}, | ||
| 668 | {"NonDeviceIpc ", Kernel::Svc::MemoryState::NonDeviceIpc}, | ||
| 669 | {"Kernel ", Kernel::Svc::MemoryState::Kernel}, | ||
| 670 | {"GeneratedCode ", Kernel::Svc::MemoryState::GeneratedCode}, | ||
| 671 | {"CodeOut ", Kernel::Svc::MemoryState::CodeOut}, | ||
| 672 | {"Coverage ", Kernel::Svc::MemoryState::Coverage}, | ||
| 673 | }}; | ||
| 674 | |||
| 675 | static constexpr const char* GetMemoryStateName(Kernel::Svc::MemoryState state) { | ||
| 676 | for (size_t i = 0; i < MemoryStateNames.size(); i++) { | ||
| 677 | if (std::get<1>(MemoryStateNames[i]) == state) { | ||
| 678 | return std::get<0>(MemoryStateNames[i]); | ||
| 679 | } | ||
| 680 | } | ||
| 681 | return "Unknown "; | ||
| 682 | } | ||
| 683 | |||
| 684 | static constexpr const char* GetMemoryPermissionString(const Kernel::Svc::MemoryInfo& info) { | ||
| 685 | if (info.state == Kernel::Svc::MemoryState::Free) { | ||
| 686 | return " "; | ||
| 687 | } | ||
| 688 | |||
| 689 | switch (info.permission) { | ||
| 690 | case Kernel::Svc::MemoryPermission::ReadExecute: | ||
| 691 | return "r-x"; | ||
| 692 | case Kernel::Svc::MemoryPermission::Read: | ||
| 693 | return "r--"; | ||
| 694 | case Kernel::Svc::MemoryPermission::ReadWrite: | ||
| 695 | return "rw-"; | ||
| 696 | default: | ||
| 697 | return "---"; | ||
| 698 | } | ||
| 699 | } | ||
| 700 | |||
| 701 | static VAddr GetModuleEnd(Kernel::KPageTable& page_table, VAddr base) { | ||
| 702 | Kernel::Svc::MemoryInfo mem_info; | ||
| 703 | VAddr cur_addr{base}; | ||
| 704 | |||
| 705 | // Expect: r-x Code (.text) | ||
| 706 | mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo(); | ||
| 707 | cur_addr = mem_info.base_address + mem_info.size; | ||
| 708 | if (mem_info.state != Kernel::Svc::MemoryState::Code || | ||
| 709 | mem_info.permission != Kernel::Svc::MemoryPermission::ReadExecute) { | ||
| 710 | return cur_addr - 1; | ||
| 711 | } | ||
| 712 | |||
| 713 | // Expect: r-- Code (.rodata) | ||
| 714 | mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo(); | ||
| 715 | cur_addr = mem_info.base_address + mem_info.size; | ||
| 716 | if (mem_info.state != Kernel::Svc::MemoryState::Code || | ||
| 717 | mem_info.permission != Kernel::Svc::MemoryPermission::Read) { | ||
| 718 | return cur_addr - 1; | ||
| 719 | } | ||
| 720 | |||
| 721 | // Expect: rw- CodeData (.data) | ||
| 722 | mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo(); | ||
| 723 | cur_addr = mem_info.base_address + mem_info.size; | ||
| 724 | return cur_addr - 1; | ||
| 725 | } | ||
| 726 | |||
| 727 | void GDBStub::HandleRcmd(const std::vector<u8>& command) { | ||
| 728 | std::string_view command_str{reinterpret_cast<const char*>(&command[0]), command.size()}; | ||
| 729 | std::string reply; | ||
| 730 | |||
| 731 | auto* process = system.CurrentProcess(); | ||
| 732 | auto& page_table = process->PageTable(); | ||
| 733 | |||
| 734 | if (command_str == "get info") { | ||
| 735 | Loader::AppLoader::Modules modules; | ||
| 736 | system.GetAppLoader().ReadNSOModules(modules); | ||
| 737 | |||
| 738 | reply = fmt::format("Process: {:#x} ({})\n" | ||
| 739 | "Program Id: {:#018x}\n", | ||
| 740 | process->GetProcessID(), process->GetName(), process->GetProgramID()); | ||
| 741 | reply += | ||
| 742 | fmt::format("Layout:\n" | ||
| 743 | " Alias: {:#012x} - {:#012x}\n" | ||
| 744 | " Heap: {:#012x} - {:#012x}\n" | ||
| 745 | " Aslr: {:#012x} - {:#012x}\n" | ||
| 746 | " Stack: {:#012x} - {:#012x}\n" | ||
| 747 | "Modules:\n", | ||
| 748 | page_table.GetAliasRegionStart(), page_table.GetAliasRegionEnd(), | ||
| 749 | page_table.GetHeapRegionStart(), page_table.GetHeapRegionEnd(), | ||
| 750 | page_table.GetAliasCodeRegionStart(), page_table.GetAliasCodeRegionEnd(), | ||
| 751 | page_table.GetStackRegionStart(), page_table.GetStackRegionEnd()); | ||
| 752 | |||
| 753 | for (const auto& [vaddr, name] : modules) { | ||
| 754 | reply += fmt::format(" {:#012x} - {:#012x} {}\n", vaddr, | ||
| 755 | GetModuleEnd(page_table, vaddr), name); | ||
| 756 | } | ||
| 757 | } else if (command_str == "get mappings") { | ||
| 758 | reply = "Mappings:\n"; | ||
| 759 | VAddr cur_addr = 0; | ||
| 760 | |||
| 761 | while (true) { | ||
| 762 | using MemoryAttribute = Kernel::Svc::MemoryAttribute; | ||
| 763 | |||
| 764 | auto mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo(); | ||
| 765 | |||
| 766 | if (mem_info.state != Kernel::Svc::MemoryState::Inaccessible || | ||
| 767 | mem_info.base_address + mem_info.size - 1 != std::numeric_limits<u64>::max()) { | ||
| 768 | const char* state = GetMemoryStateName(mem_info.state); | ||
| 769 | const char* perm = GetMemoryPermissionString(mem_info); | ||
| 770 | |||
| 771 | const char l = True(mem_info.attribute & MemoryAttribute::Locked) ? 'L' : '-'; | ||
| 772 | const char i = True(mem_info.attribute & MemoryAttribute::IpcLocked) ? 'I' : '-'; | ||
| 773 | const char d = True(mem_info.attribute & MemoryAttribute::DeviceShared) ? 'D' : '-'; | ||
| 774 | const char u = True(mem_info.attribute & MemoryAttribute::Uncached) ? 'U' : '-'; | ||
| 775 | |||
| 776 | reply += | ||
| 777 | fmt::format(" {:#012x} - {:#012x} {} {} {}{}{}{} [{}, {}]\n", | ||
| 778 | mem_info.base_address, mem_info.base_address + mem_info.size - 1, | ||
| 779 | perm, state, l, i, d, u, mem_info.ipc_count, mem_info.device_count); | ||
| 780 | } | ||
| 781 | |||
| 782 | const uintptr_t next_address = mem_info.base_address + mem_info.size; | ||
| 783 | if (next_address <= cur_addr) { | ||
| 784 | break; | ||
| 785 | } | ||
| 786 | |||
| 787 | cur_addr = next_address; | ||
| 788 | } | ||
| 789 | } else if (command_str == "help") { | ||
| 790 | reply = "Commands:\n get info\n get mappings\n"; | ||
| 791 | } else { | ||
| 792 | reply = "Unknown command.\nCommands:\n get info\n get mappings\n"; | ||
| 793 | } | ||
| 794 | |||
| 795 | std::span<const u8> reply_span{reinterpret_cast<u8*>(&reply.front()), reply.size()}; | ||
| 796 | SendReply(Common::HexToString(reply_span, false)); | ||
| 797 | } | ||
| 798 | |||
| 648 | Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) { | 799 | Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) { |
| 649 | const auto& threads{system.GlobalSchedulerContext().GetThreadList()}; | 800 | const auto& threads{system.GlobalSchedulerContext().GetThreadList()}; |
| 650 | for (auto* thread : threads) { | 801 | for (auto* thread : threads) { |
diff --git a/src/core/debugger/gdbstub.h b/src/core/debugger/gdbstub.h index 0b0f56e4b..368197920 100644 --- a/src/core/debugger/gdbstub.h +++ b/src/core/debugger/gdbstub.h | |||
| @@ -32,6 +32,7 @@ private: | |||
| 32 | void ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions); | 32 | void ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions); |
| 33 | void HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions); | 33 | void HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions); |
| 34 | void HandleQuery(std::string_view command); | 34 | void HandleQuery(std::string_view command); |
| 35 | void HandleRcmd(const std::vector<u8>& command); | ||
| 35 | void HandleBreakpointInsert(std::string_view command); | 36 | void HandleBreakpointInsert(std::string_view command); |
| 36 | void HandleBreakpointRemove(std::string_view command); | 37 | void HandleBreakpointRemove(std::string_view command); |
| 37 | std::vector<char>::const_iterator CommandEnd() const; | 38 | std::vector<char>::const_iterator CommandEnd() const; |
diff --git a/src/core/hle/kernel/k_event.cpp b/src/core/hle/kernel/k_event.cpp index 27f70e5c5..d973853ab 100644 --- a/src/core/hle/kernel/k_event.cpp +++ b/src/core/hle/kernel/k_event.cpp | |||
| @@ -20,8 +20,12 @@ void KEvent::Initialize(KProcess* owner) { | |||
| 20 | m_readable_event.Initialize(this); | 20 | m_readable_event.Initialize(this); |
| 21 | 21 | ||
| 22 | // Set our owner process. | 22 | // Set our owner process. |
| 23 | m_owner = owner; | 23 | // HACK: this should never be nullptr, but service threads don't have a |
| 24 | m_owner->Open(); | 24 | // proper parent process yet. |
| 25 | if (owner != nullptr) { | ||
| 26 | m_owner = owner; | ||
| 27 | m_owner->Open(); | ||
| 28 | } | ||
| 25 | 29 | ||
| 26 | // Mark initialized. | 30 | // Mark initialized. |
| 27 | m_initialized = true; | 31 | m_initialized = true; |
| @@ -50,8 +54,11 @@ Result KEvent::Clear() { | |||
| 50 | void KEvent::PostDestroy(uintptr_t arg) { | 54 | void KEvent::PostDestroy(uintptr_t arg) { |
| 51 | // Release the event count resource the owner process holds. | 55 | // Release the event count resource the owner process holds. |
| 52 | KProcess* owner = reinterpret_cast<KProcess*>(arg); | 56 | KProcess* owner = reinterpret_cast<KProcess*>(arg); |
| 53 | owner->GetResourceLimit()->Release(LimitableResource::EventCountMax, 1); | 57 | |
| 54 | owner->Close(); | 58 | if (owner != nullptr) { |
| 59 | owner->GetResourceLimit()->Release(LimitableResource::EventCountMax, 1); | ||
| 60 | owner->Close(); | ||
| 61 | } | ||
| 55 | } | 62 | } |
| 56 | 63 | ||
| 57 | } // namespace Kernel | 64 | } // namespace Kernel |
diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h index 950850291..f1ca785d7 100644 --- a/src/core/hle/kernel/k_page_table.h +++ b/src/core/hle/kernel/k_page_table.h | |||
| @@ -320,6 +320,9 @@ public: | |||
| 320 | constexpr VAddr GetAliasCodeRegionStart() const { | 320 | constexpr VAddr GetAliasCodeRegionStart() const { |
| 321 | return m_alias_code_region_start; | 321 | return m_alias_code_region_start; |
| 322 | } | 322 | } |
| 323 | constexpr VAddr GetAliasCodeRegionEnd() const { | ||
| 324 | return m_alias_code_region_end; | ||
| 325 | } | ||
| 323 | constexpr VAddr GetAliasCodeRegionSize() const { | 326 | constexpr VAddr GetAliasCodeRegionSize() const { |
| 324 | return m_alias_code_region_end - m_alias_code_region_start; | 327 | return m_alias_code_region_end - m_alias_code_region_start; |
| 325 | } | 328 | } |
diff --git a/src/core/hle/kernel/service_thread.cpp b/src/core/hle/kernel/service_thread.cpp index f5c2ab23f..e6e41ac34 100644 --- a/src/core/hle/kernel/service_thread.cpp +++ b/src/core/hle/kernel/service_thread.cpp | |||
| @@ -40,7 +40,6 @@ private: | |||
| 40 | std::mutex m_session_mutex; | 40 | std::mutex m_session_mutex; |
| 41 | std::map<KServerSession*, std::shared_ptr<SessionRequestManager>> m_sessions; | 41 | std::map<KServerSession*, std::shared_ptr<SessionRequestManager>> m_sessions; |
| 42 | KEvent* m_wakeup_event; | 42 | KEvent* m_wakeup_event; |
| 43 | KProcess* m_process; | ||
| 44 | KThread* m_thread; | 43 | KThread* m_thread; |
| 45 | std::atomic<bool> m_shutdown_requested; | 44 | std::atomic<bool> m_shutdown_requested; |
| 46 | const std::string m_service_name; | 45 | const std::string m_service_name; |
| @@ -180,39 +179,17 @@ ServiceThread::Impl::~Impl() { | |||
| 180 | 179 | ||
| 181 | // Close thread. | 180 | // Close thread. |
| 182 | m_thread->Close(); | 181 | m_thread->Close(); |
| 183 | |||
| 184 | // Close process. | ||
| 185 | m_process->Close(); | ||
| 186 | } | 182 | } |
| 187 | 183 | ||
| 188 | ServiceThread::Impl::Impl(KernelCore& kernel_, const std::string& service_name) | 184 | ServiceThread::Impl::Impl(KernelCore& kernel_, const std::string& service_name) |
| 189 | : kernel{kernel_}, m_service_name{service_name} { | 185 | : kernel{kernel_}, m_service_name{service_name} { |
| 190 | // Initialize process. | ||
| 191 | m_process = KProcess::Create(kernel); | ||
| 192 | KProcess::Initialize(m_process, kernel.System(), service_name, | ||
| 193 | KProcess::ProcessType::KernelInternal, kernel.GetSystemResourceLimit()); | ||
| 194 | |||
| 195 | // Reserve a new event from the process resource limit | ||
| 196 | KScopedResourceReservation event_reservation(m_process, LimitableResource::EventCountMax); | ||
| 197 | ASSERT(event_reservation.Succeeded()); | ||
| 198 | |||
| 199 | // Initialize event. | 186 | // Initialize event. |
| 200 | m_wakeup_event = KEvent::Create(kernel); | 187 | m_wakeup_event = KEvent::Create(kernel); |
| 201 | m_wakeup_event->Initialize(m_process); | 188 | m_wakeup_event->Initialize(nullptr); |
| 202 | |||
| 203 | // Commit the event reservation. | ||
| 204 | event_reservation.Commit(); | ||
| 205 | |||
| 206 | // Reserve a new thread from the process resource limit | ||
| 207 | KScopedResourceReservation thread_reservation(m_process, LimitableResource::ThreadCountMax); | ||
| 208 | ASSERT(thread_reservation.Succeeded()); | ||
| 209 | 189 | ||
| 210 | // Initialize thread. | 190 | // Initialize thread. |
| 211 | m_thread = KThread::Create(kernel); | 191 | m_thread = KThread::Create(kernel); |
| 212 | ASSERT(KThread::InitializeDummyThread(m_thread, m_process).IsSuccess()); | 192 | ASSERT(KThread::InitializeDummyThread(m_thread, nullptr).IsSuccess()); |
| 213 | |||
| 214 | // Commit the thread reservation. | ||
| 215 | thread_reservation.Commit(); | ||
| 216 | 193 | ||
| 217 | // Start thread. | 194 | // Start thread. |
| 218 | m_host_thread = std::jthread([this] { LoopProcess(); }); | 195 | m_host_thread = std::jthread([this] { LoopProcess(); }); |
diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 56c990728..240f06689 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h | |||
| @@ -28,30 +28,49 @@ enum class ErrorModule : u32 { | |||
| 28 | Loader = 9, | 28 | Loader = 9, |
| 29 | CMIF = 10, | 29 | CMIF = 10, |
| 30 | HIPC = 11, | 30 | HIPC = 11, |
| 31 | TMA = 12, | ||
| 32 | DMNT = 13, | ||
| 33 | GDS = 14, | ||
| 31 | PM = 15, | 34 | PM = 15, |
| 32 | NS = 16, | 35 | NS = 16, |
| 36 | BSDSockets = 17, | ||
| 33 | HTC = 18, | 37 | HTC = 18, |
| 38 | TSC = 19, | ||
| 34 | NCMContent = 20, | 39 | NCMContent = 20, |
| 35 | SM = 21, | 40 | SM = 21, |
| 36 | RO = 22, | 41 | RO = 22, |
| 42 | GC = 23, | ||
| 37 | SDMMC = 24, | 43 | SDMMC = 24, |
| 38 | OVLN = 25, | 44 | OVLN = 25, |
| 39 | SPL = 26, | 45 | SPL = 26, |
| 46 | Socket = 27, | ||
| 47 | HTCLOW = 29, | ||
| 48 | DDSF = 30, | ||
| 49 | HTCFS = 31, | ||
| 50 | Async = 32, | ||
| 51 | Util = 33, | ||
| 52 | TIPC = 35, | ||
| 53 | ANIF = 37, | ||
| 40 | ETHC = 100, | 54 | ETHC = 100, |
| 41 | I2C = 101, | 55 | I2C = 101, |
| 42 | GPIO = 102, | 56 | GPIO = 102, |
| 43 | UART = 103, | 57 | UART = 103, |
| 58 | CPAD = 104, | ||
| 44 | Settings = 105, | 59 | Settings = 105, |
| 60 | FTM = 106, | ||
| 45 | WLAN = 107, | 61 | WLAN = 107, |
| 46 | XCD = 108, | 62 | XCD = 108, |
| 63 | TMP451 = 109, | ||
| 47 | NIFM = 110, | 64 | NIFM = 110, |
| 48 | Hwopus = 111, | 65 | Hwopus = 111, |
| 66 | LSM6DS3 = 112, | ||
| 49 | Bluetooth = 113, | 67 | Bluetooth = 113, |
| 50 | VI = 114, | 68 | VI = 114, |
| 51 | NFP = 115, | 69 | NFP = 115, |
| 52 | Time = 116, | 70 | Time = 116, |
| 53 | FGM = 117, | 71 | FGM = 117, |
| 54 | OE = 118, | 72 | OE = 118, |
| 73 | BH1730FVC = 119, | ||
| 55 | PCIe = 120, | 74 | PCIe = 120, |
| 56 | Friends = 121, | 75 | Friends = 121, |
| 57 | BCAT = 122, | 76 | BCAT = 122, |
| @@ -65,7 +84,7 @@ enum class ErrorModule : u32 { | |||
| 65 | AHID = 130, | 84 | AHID = 130, |
| 66 | Qlaunch = 132, | 85 | Qlaunch = 132, |
| 67 | PCV = 133, | 86 | PCV = 133, |
| 68 | OMM = 134, | 87 | USBPD = 134, |
| 69 | BPC = 135, | 88 | BPC = 135, |
| 70 | PSM = 136, | 89 | PSM = 136, |
| 71 | NIM = 137, | 90 | NIM = 137, |
| @@ -75,18 +94,22 @@ enum class ErrorModule : u32 { | |||
| 75 | NSD = 141, | 94 | NSD = 141, |
| 76 | PCTL = 142, | 95 | PCTL = 142, |
| 77 | BTM = 143, | 96 | BTM = 143, |
| 97 | LA = 144, | ||
| 78 | ETicket = 145, | 98 | ETicket = 145, |
| 79 | NGC = 146, | 99 | NGC = 146, |
| 80 | ERPT = 147, | 100 | ERPT = 147, |
| 81 | APM = 148, | 101 | APM = 148, |
| 102 | CEC = 149, | ||
| 82 | Profiler = 150, | 103 | Profiler = 150, |
| 83 | ErrorUpload = 151, | 104 | ErrorUpload = 151, |
| 105 | LIDBE = 152, | ||
| 84 | Audio = 153, | 106 | Audio = 153, |
| 85 | NPNS = 154, | 107 | NPNS = 154, |
| 86 | NPNSHTTPSTREAM = 155, | 108 | NPNSHTTPSTREAM = 155, |
| 87 | ARP = 157, | 109 | ARP = 157, |
| 88 | SWKBD = 158, | 110 | SWKBD = 158, |
| 89 | BOOT = 159, | 111 | BOOT = 159, |
| 112 | NetDiag = 160, | ||
| 90 | NFCMifare = 161, | 113 | NFCMifare = 161, |
| 91 | UserlandAssert = 162, | 114 | UserlandAssert = 162, |
| 92 | Fatal = 163, | 115 | Fatal = 163, |
| @@ -94,17 +117,68 @@ enum class ErrorModule : u32 { | |||
| 94 | SPSM = 165, | 117 | SPSM = 165, |
| 95 | BGTC = 167, | 118 | BGTC = 167, |
| 96 | UserlandCrash = 168, | 119 | UserlandCrash = 168, |
| 120 | SASBUS = 169, | ||
| 121 | PI = 170, | ||
| 122 | AudioCtrl = 172, | ||
| 123 | LBL = 173, | ||
| 124 | JIT = 175, | ||
| 125 | HDCP = 176, | ||
| 126 | OMM = 177, | ||
| 127 | PDM = 178, | ||
| 128 | OLSC = 179, | ||
| 97 | SREPO = 180, | 129 | SREPO = 180, |
| 98 | Dauth = 181, | 130 | Dauth = 181, |
| 131 | STDFU = 182, | ||
| 132 | DBG = 183, | ||
| 133 | DHCPS = 186, | ||
| 134 | SPI = 187, | ||
| 135 | AVM = 188, | ||
| 136 | PWM = 189, | ||
| 137 | RTC = 191, | ||
| 138 | Regulator = 192, | ||
| 139 | LED = 193, | ||
| 140 | SIO = 195, | ||
| 141 | PCM = 196, | ||
| 142 | CLKRST = 197, | ||
| 143 | POWCTL = 198, | ||
| 144 | AudioOld = 201, | ||
| 99 | HID = 202, | 145 | HID = 202, |
| 100 | LDN = 203, | 146 | LDN = 203, |
| 147 | CS = 204, | ||
| 101 | Irsensor = 205, | 148 | Irsensor = 205, |
| 102 | Capture = 206, | 149 | Capture = 206, |
| 103 | Manu = 208, | 150 | Manu = 208, |
| 104 | ATK = 209, | 151 | ATK = 209, |
| 152 | WEB = 210, | ||
| 153 | LCS = 211, | ||
| 105 | GRC = 212, | 154 | GRC = 212, |
| 155 | Repair = 213, | ||
| 156 | Album = 214, | ||
| 157 | RID = 215, | ||
| 106 | Migration = 216, | 158 | Migration = 216, |
| 107 | MigrationLdcServ = 217, | 159 | MigrationLdcServ = 217, |
| 160 | HIDBUS = 218, | ||
| 161 | ENS = 219, | ||
| 162 | WebSocket = 223, | ||
| 163 | DCDMTP = 227, | ||
| 164 | PGL = 228, | ||
| 165 | Notification = 229, | ||
| 166 | INS = 230, | ||
| 167 | LP2P = 231, | ||
| 168 | RCD = 232, | ||
| 169 | LCM40607 = 233, | ||
| 170 | PRC = 235, | ||
| 171 | TMAHTC = 237, | ||
| 172 | ECTX = 238, | ||
| 173 | MNPP = 239, | ||
| 174 | HSHL = 240, | ||
| 175 | CAPMTP = 242, | ||
| 176 | DP2HDMI = 244, | ||
| 177 | Cradle = 245, | ||
| 178 | SProfile = 246, | ||
| 179 | NDRM = 250, | ||
| 180 | TSPM = 499, | ||
| 181 | DevMenu = 500, | ||
| 108 | GeneralWebApplet = 800, | 182 | GeneralWebApplet = 800, |
| 109 | WifiWebAuthApplet = 809, | 183 | WifiWebAuthApplet = 809, |
| 110 | WhitelistedApplet = 810, | 184 | WhitelistedApplet = 810, |
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp index 0a7d42dda..d6562c842 100644 --- a/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp +++ b/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp | |||
| @@ -379,6 +379,18 @@ void EmitInvocationId(EmitContext& ctx, IR::Inst& inst) { | |||
| 379 | ctx.Add("MOV.S {}.x,primitive_invocation.x;", inst); | 379 | ctx.Add("MOV.S {}.x,primitive_invocation.x;", inst); |
| 380 | } | 380 | } |
| 381 | 381 | ||
| 382 | void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst) { | ||
| 383 | switch (ctx.stage) { | ||
| 384 | case Stage::TessellationControl: | ||
| 385 | case Stage::TessellationEval: | ||
| 386 | ctx.Add("SHL.U {}.x,primitive.vertexcount,16;", inst); | ||
| 387 | break; | ||
| 388 | default: | ||
| 389 | LOG_WARNING(Shader, "(STUBBED) called"); | ||
| 390 | ctx.Add("MOV.S {}.x,0x00ff0000;", inst); | ||
| 391 | } | ||
| 392 | } | ||
| 393 | |||
| 382 | void EmitSampleId(EmitContext& ctx, IR::Inst& inst) { | 394 | void EmitSampleId(EmitContext& ctx, IR::Inst& inst) { |
| 383 | ctx.Add("MOV.S {}.x,fragment.sampleid.x;", inst); | 395 | ctx.Add("MOV.S {}.x,fragment.sampleid.x;", inst); |
| 384 | } | 396 | } |
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h b/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h index d645fd532..eaaf9ba39 100644 --- a/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h +++ b/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h | |||
| @@ -69,6 +69,7 @@ void EmitSetOFlag(EmitContext& ctx); | |||
| 69 | void EmitWorkgroupId(EmitContext& ctx, IR::Inst& inst); | 69 | void EmitWorkgroupId(EmitContext& ctx, IR::Inst& inst); |
| 70 | void EmitLocalInvocationId(EmitContext& ctx, IR::Inst& inst); | 70 | void EmitLocalInvocationId(EmitContext& ctx, IR::Inst& inst); |
| 71 | void EmitInvocationId(EmitContext& ctx, IR::Inst& inst); | 71 | void EmitInvocationId(EmitContext& ctx, IR::Inst& inst); |
| 72 | void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst); | ||
| 72 | void EmitSampleId(EmitContext& ctx, IR::Inst& inst); | 73 | void EmitSampleId(EmitContext& ctx, IR::Inst& inst); |
| 73 | void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst); | 74 | void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst); |
| 74 | void EmitYDirection(EmitContext& ctx, IR::Inst& inst); | 75 | void EmitYDirection(EmitContext& ctx, IR::Inst& inst); |
diff --git a/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp b/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp index 89603c1c4..333a91cc5 100644 --- a/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp +++ b/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp | |||
| @@ -95,6 +95,10 @@ EmitContext::EmitContext(IR::Program& program, Bindings& bindings, const Profile | |||
| 95 | if (info.uses_invocation_id) { | 95 | if (info.uses_invocation_id) { |
| 96 | Add("ATTRIB primitive_invocation=primitive.invocation;"); | 96 | Add("ATTRIB primitive_invocation=primitive.invocation;"); |
| 97 | } | 97 | } |
| 98 | if (info.uses_invocation_info && | ||
| 99 | (stage == Stage::TessellationControl || stage == Stage::TessellationEval)) { | ||
| 100 | Add("ATTRIB primitive_vertexcount = primitive.vertexcount;"); | ||
| 101 | } | ||
| 98 | if (info.stores_tess_level_outer) { | 102 | if (info.stores_tess_level_outer) { |
| 99 | Add("OUTPUT result_patch_tessouter[]={{result.patch.tessouter[0..3]}};"); | 103 | Add("OUTPUT result_patch_tessouter[]={{result.patch.tessouter[0..3]}};"); |
| 100 | } | 104 | } |
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp index d7c845469..c1671c37b 100644 --- a/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp +++ b/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp | |||
| @@ -399,6 +399,18 @@ void EmitInvocationId(EmitContext& ctx, IR::Inst& inst) { | |||
| 399 | ctx.AddU32("{}=uint(gl_InvocationID);", inst); | 399 | ctx.AddU32("{}=uint(gl_InvocationID);", inst); |
| 400 | } | 400 | } |
| 401 | 401 | ||
| 402 | void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst) { | ||
| 403 | switch (ctx.stage) { | ||
| 404 | case Stage::TessellationControl: | ||
| 405 | case Stage::TessellationEval: | ||
| 406 | ctx.AddU32("{}=uint(gl_PatchVerticesIn)<<16;", inst); | ||
| 407 | break; | ||
| 408 | default: | ||
| 409 | LOG_WARNING(Shader, "(STUBBED) called"); | ||
| 410 | ctx.AddU32("{}=uint(0x00ff0000);", inst); | ||
| 411 | } | ||
| 412 | } | ||
| 413 | |||
| 402 | void EmitSampleId(EmitContext& ctx, IR::Inst& inst) { | 414 | void EmitSampleId(EmitContext& ctx, IR::Inst& inst) { |
| 403 | ctx.AddU32("{}=uint(gl_SampleID);", inst); | 415 | ctx.AddU32("{}=uint(gl_SampleID);", inst); |
| 404 | } | 416 | } |
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h b/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h index 96e683b5e..4151c89de 100644 --- a/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h +++ b/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h | |||
| @@ -83,6 +83,7 @@ void EmitSetOFlag(EmitContext& ctx); | |||
| 83 | void EmitWorkgroupId(EmitContext& ctx, IR::Inst& inst); | 83 | void EmitWorkgroupId(EmitContext& ctx, IR::Inst& inst); |
| 84 | void EmitLocalInvocationId(EmitContext& ctx, IR::Inst& inst); | 84 | void EmitLocalInvocationId(EmitContext& ctx, IR::Inst& inst); |
| 85 | void EmitInvocationId(EmitContext& ctx, IR::Inst& inst); | 85 | void EmitInvocationId(EmitContext& ctx, IR::Inst& inst); |
| 86 | void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst); | ||
| 86 | void EmitSampleId(EmitContext& ctx, IR::Inst& inst); | 87 | void EmitSampleId(EmitContext& ctx, IR::Inst& inst); |
| 87 | void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst); | 88 | void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst); |
| 88 | void EmitYDirection(EmitContext& ctx, IR::Inst& inst); | 89 | void EmitYDirection(EmitContext& ctx, IR::Inst& inst); |
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp index a4751b42d..5b3b5d1f3 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp | |||
| @@ -512,6 +512,18 @@ Id EmitInvocationId(EmitContext& ctx) { | |||
| 512 | return ctx.OpLoad(ctx.U32[1], ctx.invocation_id); | 512 | return ctx.OpLoad(ctx.U32[1], ctx.invocation_id); |
| 513 | } | 513 | } |
| 514 | 514 | ||
| 515 | Id EmitInvocationInfo(EmitContext& ctx) { | ||
| 516 | switch (ctx.stage) { | ||
| 517 | case Stage::TessellationControl: | ||
| 518 | case Stage::TessellationEval: | ||
| 519 | return ctx.OpShiftLeftLogical(ctx.U32[1], ctx.OpLoad(ctx.U32[1], ctx.patch_vertices_in), | ||
| 520 | ctx.Const(16u)); | ||
| 521 | default: | ||
| 522 | LOG_WARNING(Shader, "(STUBBED) called"); | ||
| 523 | return ctx.Const(0x00ff0000u); | ||
| 524 | } | ||
| 525 | } | ||
| 526 | |||
| 515 | Id EmitSampleId(EmitContext& ctx) { | 527 | Id EmitSampleId(EmitContext& ctx) { |
| 516 | return ctx.OpLoad(ctx.U32[1], ctx.sample_id); | 528 | return ctx.OpLoad(ctx.U32[1], ctx.sample_id); |
| 517 | } | 529 | } |
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index 7070c8fda..e31cdc5e8 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h | |||
| @@ -72,6 +72,7 @@ void EmitSetOFlag(EmitContext& ctx); | |||
| 72 | Id EmitWorkgroupId(EmitContext& ctx); | 72 | Id EmitWorkgroupId(EmitContext& ctx); |
| 73 | Id EmitLocalInvocationId(EmitContext& ctx); | 73 | Id EmitLocalInvocationId(EmitContext& ctx); |
| 74 | Id EmitInvocationId(EmitContext& ctx); | 74 | Id EmitInvocationId(EmitContext& ctx); |
| 75 | Id EmitInvocationInfo(EmitContext& ctx); | ||
| 75 | Id EmitSampleId(EmitContext& ctx); | 76 | Id EmitSampleId(EmitContext& ctx); |
| 76 | Id EmitIsHelperInvocation(EmitContext& ctx); | 77 | Id EmitIsHelperInvocation(EmitContext& ctx); |
| 77 | Id EmitYDirection(EmitContext& ctx); | 78 | Id EmitYDirection(EmitContext& ctx); |
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index c26ad8f93..0bfc2dd89 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp | |||
| @@ -1325,6 +1325,10 @@ void EmitContext::DefineInputs(const IR::Program& program) { | |||
| 1325 | if (info.uses_invocation_id) { | 1325 | if (info.uses_invocation_id) { |
| 1326 | invocation_id = DefineInput(*this, U32[1], false, spv::BuiltIn::InvocationId); | 1326 | invocation_id = DefineInput(*this, U32[1], false, spv::BuiltIn::InvocationId); |
| 1327 | } | 1327 | } |
| 1328 | if (info.uses_invocation_info && | ||
| 1329 | (stage == Shader::Stage::TessellationControl || stage == Shader::Stage::TessellationEval)) { | ||
| 1330 | patch_vertices_in = DefineInput(*this, U32[1], false, spv::BuiltIn::PatchVertices); | ||
| 1331 | } | ||
| 1328 | if (info.uses_sample_id) { | 1332 | if (info.uses_sample_id) { |
| 1329 | sample_id = DefineInput(*this, U32[1], false, spv::BuiltIn::SampleId); | 1333 | sample_id = DefineInput(*this, U32[1], false, spv::BuiltIn::SampleId); |
| 1330 | } | 1334 | } |
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index c86e50911..dde45b4bc 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h | |||
| @@ -204,6 +204,7 @@ public: | |||
| 204 | Id workgroup_id{}; | 204 | Id workgroup_id{}; |
| 205 | Id local_invocation_id{}; | 205 | Id local_invocation_id{}; |
| 206 | Id invocation_id{}; | 206 | Id invocation_id{}; |
| 207 | Id patch_vertices_in{}; | ||
| 207 | Id sample_id{}; | 208 | Id sample_id{}; |
| 208 | Id is_helper_invocation{}; | 209 | Id is_helper_invocation{}; |
| 209 | Id subgroup_local_invocation_id{}; | 210 | Id subgroup_local_invocation_id{}; |
diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.cpp b/src/shader_recompiler/frontend/ir/ir_emitter.cpp index d4425f06d..0cdac0eff 100644 --- a/src/shader_recompiler/frontend/ir/ir_emitter.cpp +++ b/src/shader_recompiler/frontend/ir/ir_emitter.cpp | |||
| @@ -362,6 +362,10 @@ U32 IREmitter::InvocationId() { | |||
| 362 | return Inst<U32>(Opcode::InvocationId); | 362 | return Inst<U32>(Opcode::InvocationId); |
| 363 | } | 363 | } |
| 364 | 364 | ||
| 365 | U32 IREmitter::InvocationInfo() { | ||
| 366 | return Inst<U32>(Opcode::InvocationInfo); | ||
| 367 | } | ||
| 368 | |||
| 365 | U32 IREmitter::SampleId() { | 369 | U32 IREmitter::SampleId() { |
| 366 | return Inst<U32>(Opcode::SampleId); | 370 | return Inst<U32>(Opcode::SampleId); |
| 367 | } | 371 | } |
diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.h b/src/shader_recompiler/frontend/ir/ir_emitter.h index f163c18d9..2df992feb 100644 --- a/src/shader_recompiler/frontend/ir/ir_emitter.h +++ b/src/shader_recompiler/frontend/ir/ir_emitter.h | |||
| @@ -97,6 +97,7 @@ public: | |||
| 97 | [[nodiscard]] U32 LocalInvocationIdZ(); | 97 | [[nodiscard]] U32 LocalInvocationIdZ(); |
| 98 | 98 | ||
| 99 | [[nodiscard]] U32 InvocationId(); | 99 | [[nodiscard]] U32 InvocationId(); |
| 100 | [[nodiscard]] U32 InvocationInfo(); | ||
| 100 | [[nodiscard]] U32 SampleId(); | 101 | [[nodiscard]] U32 SampleId(); |
| 101 | [[nodiscard]] U1 IsHelperInvocation(); | 102 | [[nodiscard]] U1 IsHelperInvocation(); |
| 102 | [[nodiscard]] F32 YDirection(); | 103 | [[nodiscard]] F32 YDirection(); |
diff --git a/src/shader_recompiler/frontend/ir/opcodes.inc b/src/shader_recompiler/frontend/ir/opcodes.inc index 88aa077ee..1fe3749cc 100644 --- a/src/shader_recompiler/frontend/ir/opcodes.inc +++ b/src/shader_recompiler/frontend/ir/opcodes.inc | |||
| @@ -59,6 +59,7 @@ OPCODE(SetOFlag, Void, U1, | |||
| 59 | OPCODE(WorkgroupId, U32x3, ) | 59 | OPCODE(WorkgroupId, U32x3, ) |
| 60 | OPCODE(LocalInvocationId, U32x3, ) | 60 | OPCODE(LocalInvocationId, U32x3, ) |
| 61 | OPCODE(InvocationId, U32, ) | 61 | OPCODE(InvocationId, U32, ) |
| 62 | OPCODE(InvocationInfo, U32, ) | ||
| 62 | OPCODE(SampleId, U32, ) | 63 | OPCODE(SampleId, U32, ) |
| 63 | OPCODE(IsHelperInvocation, U1, ) | 64 | OPCODE(IsHelperInvocation, U1, ) |
| 64 | OPCODE(YDirection, F32, ) | 65 | OPCODE(YDirection, F32, ) |
diff --git a/src/shader_recompiler/frontend/ir/patch.h b/src/shader_recompiler/frontend/ir/patch.h index 1e37c8eb6..5077e56c2 100644 --- a/src/shader_recompiler/frontend/ir/patch.h +++ b/src/shader_recompiler/frontend/ir/patch.h | |||
| @@ -14,8 +14,6 @@ enum class Patch : u64 { | |||
| 14 | TessellationLodBottom, | 14 | TessellationLodBottom, |
| 15 | TessellationLodInteriorU, | 15 | TessellationLodInteriorU, |
| 16 | TessellationLodInteriorV, | 16 | TessellationLodInteriorV, |
| 17 | ComponentPadding0, | ||
| 18 | ComponentPadding1, | ||
| 19 | Component0, | 17 | Component0, |
| 20 | Component1, | 18 | Component1, |
| 21 | Component2, | 19 | Component2, |
| @@ -137,7 +135,7 @@ enum class Patch : u64 { | |||
| 137 | Component118, | 135 | Component118, |
| 138 | Component119, | 136 | Component119, |
| 139 | }; | 137 | }; |
| 140 | static_assert(static_cast<u64>(Patch::Component119) == 127); | 138 | static_assert(static_cast<u64>(Patch::Component119) == 125); |
| 141 | 139 | ||
| 142 | [[nodiscard]] bool IsGeneric(Patch patch) noexcept; | 140 | [[nodiscard]] bool IsGeneric(Patch patch) noexcept; |
| 143 | 141 | ||
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp index 52be12f9c..753c62098 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp | |||
| @@ -117,8 +117,7 @@ enum class SpecialRegister : u64 { | |||
| 117 | case SpecialRegister::SR_THREAD_KILL: | 117 | case SpecialRegister::SR_THREAD_KILL: |
| 118 | return IR::U32{ir.Select(ir.IsHelperInvocation(), ir.Imm32(-1), ir.Imm32(0))}; | 118 | return IR::U32{ir.Select(ir.IsHelperInvocation(), ir.Imm32(-1), ir.Imm32(0))}; |
| 119 | case SpecialRegister::SR_INVOCATION_INFO: | 119 | case SpecialRegister::SR_INVOCATION_INFO: |
| 120 | LOG_WARNING(Shader, "(STUBBED) SR_INVOCATION_INFO"); | 120 | return ir.InvocationInfo(); |
| 121 | return ir.Imm32(0x00ff'0000); | ||
| 122 | case SpecialRegister::SR_TID: { | 121 | case SpecialRegister::SR_TID: { |
| 123 | const IR::Value tid{ir.LocalInvocationId()}; | 122 | const IR::Value tid{ir.LocalInvocationId()}; |
| 124 | return ir.BitFieldInsert(ir.BitFieldInsert(IR::U32{ir.CompositeExtract(tid, 0)}, | 123 | return ir.BitFieldInsert(ir.BitFieldInsert(IR::U32{ir.CompositeExtract(tid, 0)}, |
diff --git a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp index 7cff8ecdc..5a4195217 100644 --- a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp +++ b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp | |||
| @@ -468,6 +468,9 @@ void VisitUsages(Info& info, IR::Inst& inst) { | |||
| 468 | case IR::Opcode::InvocationId: | 468 | case IR::Opcode::InvocationId: |
| 469 | info.uses_invocation_id = true; | 469 | info.uses_invocation_id = true; |
| 470 | break; | 470 | break; |
| 471 | case IR::Opcode::InvocationInfo: | ||
| 472 | info.uses_invocation_info = true; | ||
| 473 | break; | ||
| 471 | case IR::Opcode::SampleId: | 474 | case IR::Opcode::SampleId: |
| 472 | info.uses_sample_id = true; | 475 | info.uses_sample_id = true; |
| 473 | break; | 476 | break; |
diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h index f31e1f821..ee6252bb5 100644 --- a/src/shader_recompiler/shader_info.h +++ b/src/shader_recompiler/shader_info.h | |||
| @@ -127,6 +127,7 @@ struct Info { | |||
| 127 | bool uses_workgroup_id{}; | 127 | bool uses_workgroup_id{}; |
| 128 | bool uses_local_invocation_id{}; | 128 | bool uses_local_invocation_id{}; |
| 129 | bool uses_invocation_id{}; | 129 | bool uses_invocation_id{}; |
| 130 | bool uses_invocation_info{}; | ||
| 130 | bool uses_sample_id{}; | 131 | bool uses_sample_id{}; |
| 131 | bool uses_is_helper_invocation{}; | 132 | bool uses_is_helper_invocation{}; |
| 132 | bool uses_subgroup_invocation_id{}; | 133 | bool uses_subgroup_invocation_id{}; |
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 4a2f2c1fd..d502d181c 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp | |||
| @@ -249,6 +249,11 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume | |||
| 249 | return; | 249 | return; |
| 250 | case MAXWELL3D_REG_INDEX(fragment_barrier): | 250 | case MAXWELL3D_REG_INDEX(fragment_barrier): |
| 251 | return rasterizer->FragmentBarrier(); | 251 | return rasterizer->FragmentBarrier(); |
| 252 | case MAXWELL3D_REG_INDEX(invalidate_texture_data_cache): | ||
| 253 | rasterizer->InvalidateGPUCache(); | ||
| 254 | return rasterizer->WaitForIdle(); | ||
| 255 | case MAXWELL3D_REG_INDEX(tiled_cache_barrier): | ||
| 256 | return rasterizer->TiledCacheBarrier(); | ||
| 252 | } | 257 | } |
| 253 | } | 258 | } |
| 254 | 259 | ||
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 910ab213a..34b085388 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h | |||
| @@ -707,7 +707,7 @@ public: | |||
| 707 | case Size::Size_A2_B10_G10_R10: | 707 | case Size::Size_A2_B10_G10_R10: |
| 708 | return "2_10_10_10"; | 708 | return "2_10_10_10"; |
| 709 | case Size::Size_B10_G11_R11: | 709 | case Size::Size_B10_G11_R11: |
| 710 | return "10_11_12"; | 710 | return "10_11_11"; |
| 711 | default: | 711 | default: |
| 712 | ASSERT(false); | 712 | ASSERT(false); |
| 713 | return {}; | 713 | return {}; |
| @@ -2639,7 +2639,7 @@ public: | |||
| 2639 | L2CacheControl l2_cache_control; ///< 0x0218 | 2639 | L2CacheControl l2_cache_control; ///< 0x0218 |
| 2640 | InvalidateShaderCache invalidate_shader_cache; ///< 0x021C | 2640 | InvalidateShaderCache invalidate_shader_cache; ///< 0x021C |
| 2641 | INSERT_PADDING_BYTES_NOINIT(0xA8); | 2641 | INSERT_PADDING_BYTES_NOINIT(0xA8); |
| 2642 | SyncInfo sync_info; ///< 0x02C8 | 2642 | SyncInfo sync_info; ///< 0x02C8 |
| 2643 | INSERT_PADDING_BYTES_NOINIT(0x4); | 2643 | INSERT_PADDING_BYTES_NOINIT(0x4); |
| 2644 | u32 prim_circular_buffer_throttle; ///< 0x02D0 | 2644 | u32 prim_circular_buffer_throttle; ///< 0x02D0 |
| 2645 | u32 flush_invalidate_rop_mini_cache; ///< 0x02D4 | 2645 | u32 flush_invalidate_rop_mini_cache; ///< 0x02D4 |
| @@ -2731,7 +2731,11 @@ public: | |||
| 2731 | s32 stencil_back_ref; ///< 0x0F54 | 2731 | s32 stencil_back_ref; ///< 0x0F54 |
| 2732 | u32 stencil_back_mask; ///< 0x0F58 | 2732 | u32 stencil_back_mask; ///< 0x0F58 |
| 2733 | u32 stencil_back_func_mask; ///< 0x0F5C | 2733 | u32 stencil_back_func_mask; ///< 0x0F5C |
| 2734 | INSERT_PADDING_BYTES_NOINIT(0x24); | 2734 | INSERT_PADDING_BYTES_NOINIT(0x14); |
| 2735 | u32 invalidate_texture_data_cache; ///< 0x0F74 Assumed - Not in official docs. | ||
| 2736 | INSERT_PADDING_BYTES_NOINIT(0x4); | ||
| 2737 | u32 tiled_cache_barrier; ///< 0x0F7C Assumed - Not in official docs. | ||
| 2738 | INSERT_PADDING_BYTES_NOINIT(0x4); | ||
| 2735 | VertexStreamSubstitute vertex_stream_substitute; ///< 0x0F84 | 2739 | VertexStreamSubstitute vertex_stream_substitute; ///< 0x0F84 |
| 2736 | u32 line_mode_clip_generated_edge_do_not_draw; ///< 0x0F8C | 2740 | u32 line_mode_clip_generated_edge_do_not_draw; ///< 0x0F8C |
| 2737 | u32 color_mask_common; ///< 0x0F90 | 2741 | u32 color_mask_common; ///< 0x0F90 |
| @@ -2791,7 +2795,8 @@ public: | |||
| 2791 | FillViaTriangleMode fill_via_triangle_mode; ///< 0x113C | 2795 | FillViaTriangleMode fill_via_triangle_mode; ///< 0x113C |
| 2792 | u32 blend_per_format_snorm8_unorm16_snorm16_enabled; ///< 0x1140 | 2796 | u32 blend_per_format_snorm8_unorm16_snorm16_enabled; ///< 0x1140 |
| 2793 | u32 flush_pending_writes_sm_gloal_store; ///< 0x1144 | 2797 | u32 flush_pending_writes_sm_gloal_store; ///< 0x1144 |
| 2794 | INSERT_PADDING_BYTES_NOINIT(0x18); | 2798 | u32 conservative_raster_enable; ///< 0x1148 Assumed - Not in official docs. |
| 2799 | INSERT_PADDING_BYTES_NOINIT(0x14); | ||
| 2795 | std::array<VertexAttribute, NumVertexAttributes> vertex_attrib_format; ///< 0x1160 | 2800 | std::array<VertexAttribute, NumVertexAttributes> vertex_attrib_format; ///< 0x1160 |
| 2796 | std::array<MsaaSampleLocation, 4> multisample_sample_locations; ///< 0x11E0 | 2801 | std::array<MsaaSampleLocation, 4> multisample_sample_locations; ///< 0x11E0 |
| 2797 | u32 offset_render_target_index_by_viewport_index; ///< 0x11F0 | 2802 | u32 offset_render_target_index_by_viewport_index; ///< 0x11F0 |
| @@ -3287,6 +3292,8 @@ ASSERT_REG_POSITION(const_color_rendering, 0x0F40); | |||
| 3287 | ASSERT_REG_POSITION(stencil_back_ref, 0x0F54); | 3292 | ASSERT_REG_POSITION(stencil_back_ref, 0x0F54); |
| 3288 | ASSERT_REG_POSITION(stencil_back_mask, 0x0F58); | 3293 | ASSERT_REG_POSITION(stencil_back_mask, 0x0F58); |
| 3289 | ASSERT_REG_POSITION(stencil_back_func_mask, 0x0F5C); | 3294 | ASSERT_REG_POSITION(stencil_back_func_mask, 0x0F5C); |
| 3295 | ASSERT_REG_POSITION(invalidate_texture_data_cache, 0x0F74); | ||
| 3296 | ASSERT_REG_POSITION(tiled_cache_barrier, 0x0F7C); | ||
| 3290 | ASSERT_REG_POSITION(vertex_stream_substitute, 0x0F84); | 3297 | ASSERT_REG_POSITION(vertex_stream_substitute, 0x0F84); |
| 3291 | ASSERT_REG_POSITION(line_mode_clip_generated_edge_do_not_draw, 0x0F8C); | 3298 | ASSERT_REG_POSITION(line_mode_clip_generated_edge_do_not_draw, 0x0F8C); |
| 3292 | ASSERT_REG_POSITION(color_mask_common, 0x0F90); | 3299 | ASSERT_REG_POSITION(color_mask_common, 0x0F90); |
| @@ -3343,6 +3350,7 @@ ASSERT_REG_POSITION(post_ps_use_pre_ps_coverage, 0x1138); | |||
| 3343 | ASSERT_REG_POSITION(fill_via_triangle_mode, 0x113C); | 3350 | ASSERT_REG_POSITION(fill_via_triangle_mode, 0x113C); |
| 3344 | ASSERT_REG_POSITION(blend_per_format_snorm8_unorm16_snorm16_enabled, 0x1140); | 3351 | ASSERT_REG_POSITION(blend_per_format_snorm8_unorm16_snorm16_enabled, 0x1140); |
| 3345 | ASSERT_REG_POSITION(flush_pending_writes_sm_gloal_store, 0x1144); | 3352 | ASSERT_REG_POSITION(flush_pending_writes_sm_gloal_store, 0x1144); |
| 3353 | ASSERT_REG_POSITION(conservative_raster_enable, 0x1148); | ||
| 3346 | ASSERT_REG_POSITION(vertex_attrib_format, 0x1160); | 3354 | ASSERT_REG_POSITION(vertex_attrib_format, 0x1160); |
| 3347 | ASSERT_REG_POSITION(multisample_sample_locations, 0x11E0); | 3355 | ASSERT_REG_POSITION(multisample_sample_locations, 0x11E0); |
| 3348 | ASSERT_REG_POSITION(offset_render_target_index_by_viewport_index, 0x11F0); | 3356 | ASSERT_REG_POSITION(offset_render_target_index_by_viewport_index, 0x11F0); |
diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index 27be4af87..1bf6ca2dd 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp | |||
| @@ -102,26 +102,29 @@ void MaxwellDMA::Launch() { | |||
| 102 | const bool is_src_pitch = IsPitchKind(static_cast<PTEKind>(src_kind)); | 102 | const bool is_src_pitch = IsPitchKind(static_cast<PTEKind>(src_kind)); |
| 103 | const bool is_dst_pitch = IsPitchKind(static_cast<PTEKind>(dst_kind)); | 103 | const bool is_dst_pitch = IsPitchKind(static_cast<PTEKind>(dst_kind)); |
| 104 | if (!is_src_pitch && is_dst_pitch) { | 104 | if (!is_src_pitch && is_dst_pitch) { |
| 105 | std::vector<u8> tmp_buffer(regs.line_length_in); | 105 | UNIMPLEMENTED_IF(regs.line_length_in % 16 != 0); |
| 106 | std::vector<u8> dst_buffer(regs.line_length_in); | 106 | UNIMPLEMENTED_IF(regs.offset_in % 16 != 0); |
| 107 | memory_manager.ReadBlockUnsafe(regs.offset_in, tmp_buffer.data(), | 107 | UNIMPLEMENTED_IF(regs.offset_out % 16 != 0); |
| 108 | regs.line_length_in); | 108 | std::vector<u8> tmp_buffer(16); |
| 109 | for (u32 offset = 0; offset < regs.line_length_in; ++offset) { | 109 | for (u32 offset = 0; offset < regs.line_length_in; offset += 16) { |
| 110 | dst_buffer[offset] = | 110 | memory_manager.ReadBlockUnsafe( |
| 111 | tmp_buffer[convert_linear_2_blocklinear_addr(regs.offset_in + offset) - | 111 | convert_linear_2_blocklinear_addr(regs.offset_in + offset), |
| 112 | regs.offset_in]; | 112 | tmp_buffer.data(), tmp_buffer.size()); |
| 113 | memory_manager.WriteBlock(regs.offset_out + offset, tmp_buffer.data(), | ||
| 114 | tmp_buffer.size()); | ||
| 113 | } | 115 | } |
| 114 | memory_manager.WriteBlock(regs.offset_out, dst_buffer.data(), regs.line_length_in); | ||
| 115 | } else if (is_src_pitch && !is_dst_pitch) { | 116 | } else if (is_src_pitch && !is_dst_pitch) { |
| 116 | std::vector<u8> tmp_buffer(regs.line_length_in); | 117 | UNIMPLEMENTED_IF(regs.line_length_in % 16 != 0); |
| 117 | std::vector<u8> dst_buffer(regs.line_length_in); | 118 | UNIMPLEMENTED_IF(regs.offset_in % 16 != 0); |
| 118 | memory_manager.ReadBlockUnsafe(regs.offset_in, tmp_buffer.data(), | 119 | UNIMPLEMENTED_IF(regs.offset_out % 16 != 0); |
| 119 | regs.line_length_in); | 120 | std::vector<u8> tmp_buffer(16); |
| 120 | for (u32 offset = 0; offset < regs.line_length_in; ++offset) { | 121 | for (u32 offset = 0; offset < regs.line_length_in; offset += 16) { |
| 121 | dst_buffer[convert_linear_2_blocklinear_addr(regs.offset_out + offset) - | 122 | memory_manager.ReadBlockUnsafe(regs.offset_in + offset, tmp_buffer.data(), |
| 122 | regs.offset_out] = tmp_buffer[offset]; | 123 | tmp_buffer.size()); |
| 124 | memory_manager.WriteBlock( | ||
| 125 | convert_linear_2_blocklinear_addr(regs.offset_out + offset), | ||
| 126 | tmp_buffer.data(), tmp_buffer.size()); | ||
| 123 | } | 127 | } |
| 124 | memory_manager.WriteBlock(regs.offset_out, dst_buffer.data(), regs.line_length_in); | ||
| 125 | } else { | 128 | } else { |
| 126 | if (!accelerate.BufferCopy(regs.offset_in, regs.offset_out, regs.line_length_in)) { | 129 | if (!accelerate.BufferCopy(regs.offset_in, regs.offset_out, regs.line_length_in)) { |
| 127 | std::vector<u8> tmp_buffer(regs.line_length_in); | 130 | std::vector<u8> tmp_buffer(regs.line_length_in); |
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 8a8b5ce54..d05a5f60b 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp | |||
| @@ -770,7 +770,7 @@ void RasterizerOpenGL::SyncStencilTestState() { | |||
| 770 | 770 | ||
| 771 | if (regs.stencil_two_side_enable) { | 771 | if (regs.stencil_two_side_enable) { |
| 772 | glStencilFuncSeparate(GL_BACK, MaxwellToGL::ComparisonOp(regs.stencil_back_op.func), | 772 | glStencilFuncSeparate(GL_BACK, MaxwellToGL::ComparisonOp(regs.stencil_back_op.func), |
| 773 | regs.stencil_back_ref, regs.stencil_back_mask); | 773 | regs.stencil_back_ref, regs.stencil_back_func_mask); |
| 774 | glStencilOpSeparate(GL_BACK, MaxwellToGL::StencilOp(regs.stencil_back_op.fail), | 774 | glStencilOpSeparate(GL_BACK, MaxwellToGL::StencilOp(regs.stencil_back_op.fail), |
| 775 | MaxwellToGL::StencilOp(regs.stencil_back_op.zfail), | 775 | MaxwellToGL::StencilOp(regs.stencil_back_op.zfail), |
| 776 | MaxwellToGL::StencilOp(regs.stencil_back_op.zpass)); | 776 | MaxwellToGL::StencilOp(regs.stencil_back_op.zpass)); |
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index 4221c2774..3fe04a115 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp | |||
| @@ -76,7 +76,8 @@ Shader::RuntimeInfo MakeRuntimeInfo(const GraphicsPipelineKey& key, | |||
| 76 | } | 76 | } |
| 77 | break; | 77 | break; |
| 78 | case Shader::Stage::TessellationEval: | 78 | case Shader::Stage::TessellationEval: |
| 79 | info.tess_clockwise = key.tessellation_clockwise != 0; | 79 | // Flip the face, as OpenGL's drawing is flipped. |
| 80 | info.tess_clockwise = key.tessellation_clockwise == 0; | ||
| 80 | info.tess_primitive = [&key] { | 81 | info.tess_primitive = [&key] { |
| 81 | switch (key.tessellation_primitive) { | 82 | switch (key.tessellation_primitive) { |
| 82 | case Maxwell::Tessellation::DomainType::Isolines: | 83 | case Maxwell::Tessellation::DomainType::Isolines: |
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp index f85ed8e5b..98cc26679 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp | |||
| @@ -90,6 +90,7 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, | |||
| 90 | depth_format.Assign(static_cast<u32>(regs.zeta.format)); | 90 | depth_format.Assign(static_cast<u32>(regs.zeta.format)); |
| 91 | y_negate.Assign(regs.window_origin.mode != Maxwell::WindowOrigin::Mode::UpperLeft ? 1 : 0); | 91 | y_negate.Assign(regs.window_origin.mode != Maxwell::WindowOrigin::Mode::UpperLeft ? 1 : 0); |
| 92 | provoking_vertex_last.Assign(regs.provoking_vertex == Maxwell::ProvokingVertex::Last ? 1 : 0); | 92 | provoking_vertex_last.Assign(regs.provoking_vertex == Maxwell::ProvokingVertex::Last ? 1 : 0); |
| 93 | conservative_raster_enable.Assign(regs.conservative_raster_enable != 0 ? 1 : 0); | ||
| 93 | smooth_lines.Assign(regs.line_anti_alias_enable != 0 ? 1 : 0); | 94 | smooth_lines.Assign(regs.line_anti_alias_enable != 0 ? 1 : 0); |
| 94 | 95 | ||
| 95 | for (size_t i = 0; i < regs.rt.size(); ++i) { | 96 | for (size_t i = 0; i < regs.rt.size(); ++i) { |
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h index 43441209c..1afdef329 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h | |||
| @@ -193,6 +193,7 @@ struct FixedPipelineState { | |||
| 193 | BitField<6, 5, u32> depth_format; | 193 | BitField<6, 5, u32> depth_format; |
| 194 | BitField<11, 1, u32> y_negate; | 194 | BitField<11, 1, u32> y_negate; |
| 195 | BitField<12, 1, u32> provoking_vertex_last; | 195 | BitField<12, 1, u32> provoking_vertex_last; |
| 196 | BitField<13, 1, u32> conservative_raster_enable; | ||
| 196 | BitField<14, 1, u32> smooth_lines; | 197 | BitField<14, 1, u32> smooth_lines; |
| 197 | }; | 198 | }; |
| 198 | std::array<u8, Maxwell::NumRenderTargets> color_formats; | 199 | std::array<u8, Maxwell::NumRenderTargets> color_formats; |
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 1aa116cea..ef75c126c 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp | |||
| @@ -680,6 +680,15 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { | |||
| 680 | .lineStippleFactor = 0, | 680 | .lineStippleFactor = 0, |
| 681 | .lineStipplePattern = 0, | 681 | .lineStipplePattern = 0, |
| 682 | }; | 682 | }; |
| 683 | VkPipelineRasterizationConservativeStateCreateInfoEXT conservative_raster{ | ||
| 684 | .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_CONSERVATIVE_STATE_CREATE_INFO_EXT, | ||
| 685 | .pNext = nullptr, | ||
| 686 | .flags = 0, | ||
| 687 | .conservativeRasterizationMode = key.state.conservative_raster_enable != 0 | ||
| 688 | ? VK_CONSERVATIVE_RASTERIZATION_MODE_OVERESTIMATE_EXT | ||
| 689 | : VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT, | ||
| 690 | .extraPrimitiveOverestimationSize = 0.0f, | ||
| 691 | }; | ||
| 683 | VkPipelineRasterizationProvokingVertexStateCreateInfoEXT provoking_vertex{ | 692 | VkPipelineRasterizationProvokingVertexStateCreateInfoEXT provoking_vertex{ |
| 684 | .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_PROVOKING_VERTEX_STATE_CREATE_INFO_EXT, | 693 | .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_PROVOKING_VERTEX_STATE_CREATE_INFO_EXT, |
| 685 | .pNext = nullptr, | 694 | .pNext = nullptr, |
| @@ -690,6 +699,9 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { | |||
| 690 | if (IsLine(input_assembly_topology) && device.IsExtLineRasterizationSupported()) { | 699 | if (IsLine(input_assembly_topology) && device.IsExtLineRasterizationSupported()) { |
| 691 | line_state.pNext = std::exchange(rasterization_ci.pNext, &line_state); | 700 | line_state.pNext = std::exchange(rasterization_ci.pNext, &line_state); |
| 692 | } | 701 | } |
| 702 | if (device.IsExtConservativeRasterizationSupported()) { | ||
| 703 | conservative_raster.pNext = std::exchange(rasterization_ci.pNext, &conservative_raster); | ||
| 704 | } | ||
| 693 | if (device.IsExtProvokingVertexSupported()) { | 705 | if (device.IsExtProvokingVertexSupported()) { |
| 694 | provoking_vertex.pNext = std::exchange(rasterization_ci.pNext, &provoking_vertex); | 706 | provoking_vertex.pNext = std::exchange(rasterization_ci.pNext, &provoking_vertex); |
| 695 | } | 707 | } |
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index e216b90d9..d4b0a542a 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | |||
| @@ -166,6 +166,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program | |||
| 166 | } | 166 | } |
| 167 | break; | 167 | break; |
| 168 | case Shader::Stage::TessellationEval: | 168 | case Shader::Stage::TessellationEval: |
| 169 | info.tess_clockwise = key.state.tessellation_clockwise != 0; | ||
| 169 | info.tess_primitive = [&key] { | 170 | info.tess_primitive = [&key] { |
| 170 | const u32 raw{key.state.tessellation_primitive.Value()}; | 171 | const u32 raw{key.state.tessellation_primitive.Value()}; |
| 171 | switch (static_cast<Maxwell::Tessellation::DomainType>(raw)) { | 172 | switch (static_cast<Maxwell::Tessellation::DomainType>(raw)) { |
diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp index e5bc5c448..05f49c0d2 100644 --- a/src/yuzu/compatdb.cpp +++ b/src/yuzu/compatdb.cpp | |||
| @@ -15,12 +15,22 @@ CompatDB::CompatDB(Core::TelemetrySession& telemetry_session_, QWidget* parent) | |||
| 15 | : QWizard(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), | 15 | : QWizard(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), |
| 16 | ui{std::make_unique<Ui::CompatDB>()}, telemetry_session{telemetry_session_} { | 16 | ui{std::make_unique<Ui::CompatDB>()}, telemetry_session{telemetry_session_} { |
| 17 | ui->setupUi(this); | 17 | ui->setupUi(this); |
| 18 | connect(ui->radioButton_Perfect, &QRadioButton::clicked, this, &CompatDB::EnableNext); | 18 | |
| 19 | connect(ui->radioButton_Great, &QRadioButton::clicked, this, &CompatDB::EnableNext); | 19 | connect(ui->radioButton_GameBoot_Yes, &QRadioButton::clicked, this, &CompatDB::EnableNext); |
| 20 | connect(ui->radioButton_Okay, &QRadioButton::clicked, this, &CompatDB::EnableNext); | 20 | connect(ui->radioButton_GameBoot_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); |
| 21 | connect(ui->radioButton_Bad, &QRadioButton::clicked, this, &CompatDB::EnableNext); | 21 | connect(ui->radioButton_Gameplay_Yes, &QRadioButton::clicked, this, &CompatDB::EnableNext); |
| 22 | connect(ui->radioButton_IntroMenu, &QRadioButton::clicked, this, &CompatDB::EnableNext); | 22 | connect(ui->radioButton_Gameplay_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); |
| 23 | connect(ui->radioButton_WontBoot, &QRadioButton::clicked, this, &CompatDB::EnableNext); | 23 | connect(ui->radioButton_NoFreeze_Yes, &QRadioButton::clicked, this, &CompatDB::EnableNext); |
| 24 | connect(ui->radioButton_NoFreeze_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); | ||
| 25 | connect(ui->radioButton_Complete_Yes, &QRadioButton::clicked, this, &CompatDB::EnableNext); | ||
| 26 | connect(ui->radioButton_Complete_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); | ||
| 27 | connect(ui->radioButton_Graphical_Major, &QRadioButton::clicked, this, &CompatDB::EnableNext); | ||
| 28 | connect(ui->radioButton_Graphical_Minor, &QRadioButton::clicked, this, &CompatDB::EnableNext); | ||
| 29 | connect(ui->radioButton_Graphical_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); | ||
| 30 | connect(ui->radioButton_Audio_Major, &QRadioButton::clicked, this, &CompatDB::EnableNext); | ||
| 31 | connect(ui->radioButton_Audio_Minor, &QRadioButton::clicked, this, &CompatDB::EnableNext); | ||
| 32 | connect(ui->radioButton_Audio_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); | ||
| 33 | |||
| 24 | connect(button(NextButton), &QPushButton::clicked, this, &CompatDB::Submit); | 34 | connect(button(NextButton), &QPushButton::clicked, this, &CompatDB::Submit); |
| 25 | connect(&testcase_watcher, &QFutureWatcher<bool>::finished, this, | 35 | connect(&testcase_watcher, &QFutureWatcher<bool>::finished, this, |
| 26 | &CompatDB::OnTestcaseSubmitted); | 36 | &CompatDB::OnTestcaseSubmitted); |
| @@ -30,29 +40,82 @@ CompatDB::~CompatDB() = default; | |||
| 30 | 40 | ||
| 31 | enum class CompatDBPage { | 41 | enum class CompatDBPage { |
| 32 | Intro = 0, | 42 | Intro = 0, |
| 33 | Selection = 1, | 43 | GameBoot = 1, |
| 34 | Final = 2, | 44 | GamePlay = 2, |
| 45 | Freeze = 3, | ||
| 46 | Completion = 4, | ||
| 47 | Graphical = 5, | ||
| 48 | Audio = 6, | ||
| 49 | Final = 7, | ||
| 35 | }; | 50 | }; |
| 36 | 51 | ||
| 37 | void CompatDB::Submit() { | 52 | void CompatDB::Submit() { |
| 38 | QButtonGroup* compatibility = new QButtonGroup(this); | 53 | QButtonGroup* compatibility_GameBoot = new QButtonGroup(this); |
| 39 | compatibility->addButton(ui->radioButton_Perfect, 0); | 54 | compatibility_GameBoot->addButton(ui->radioButton_GameBoot_Yes, 0); |
| 40 | compatibility->addButton(ui->radioButton_Great, 1); | 55 | compatibility_GameBoot->addButton(ui->radioButton_GameBoot_No, 1); |
| 41 | compatibility->addButton(ui->radioButton_Okay, 2); | 56 | |
| 42 | compatibility->addButton(ui->radioButton_Bad, 3); | 57 | QButtonGroup* compatibility_Gameplay = new QButtonGroup(this); |
| 43 | compatibility->addButton(ui->radioButton_IntroMenu, 4); | 58 | compatibility_Gameplay->addButton(ui->radioButton_Gameplay_Yes, 0); |
| 44 | compatibility->addButton(ui->radioButton_WontBoot, 5); | 59 | compatibility_Gameplay->addButton(ui->radioButton_Gameplay_No, 1); |
| 60 | |||
| 61 | QButtonGroup* compatibility_NoFreeze = new QButtonGroup(this); | ||
| 62 | compatibility_NoFreeze->addButton(ui->radioButton_NoFreeze_Yes, 0); | ||
| 63 | compatibility_NoFreeze->addButton(ui->radioButton_NoFreeze_No, 1); | ||
| 64 | |||
| 65 | QButtonGroup* compatibility_Complete = new QButtonGroup(this); | ||
| 66 | compatibility_Complete->addButton(ui->radioButton_Complete_Yes, 0); | ||
| 67 | compatibility_Complete->addButton(ui->radioButton_Complete_No, 1); | ||
| 68 | |||
| 69 | QButtonGroup* compatibility_Graphical = new QButtonGroup(this); | ||
| 70 | compatibility_Graphical->addButton(ui->radioButton_Graphical_Major, 0); | ||
| 71 | compatibility_Graphical->addButton(ui->radioButton_Graphical_Minor, 1); | ||
| 72 | compatibility_Graphical->addButton(ui->radioButton_Graphical_No, 2); | ||
| 73 | |||
| 74 | QButtonGroup* compatibility_Audio = new QButtonGroup(this); | ||
| 75 | compatibility_Audio->addButton(ui->radioButton_Audio_Major, 0); | ||
| 76 | compatibility_Graphical->addButton(ui->radioButton_Audio_Minor, 1); | ||
| 77 | compatibility_Audio->addButton(ui->radioButton_Audio_No, 2); | ||
| 78 | |||
| 79 | const int compatiblity = static_cast<int>(CalculateCompatibility()); | ||
| 80 | |||
| 45 | switch ((static_cast<CompatDBPage>(currentId()))) { | 81 | switch ((static_cast<CompatDBPage>(currentId()))) { |
| 46 | case CompatDBPage::Selection: | 82 | case CompatDBPage::Intro: |
| 47 | if (compatibility->checkedId() == -1) { | 83 | break; |
| 84 | case CompatDBPage::GameBoot: | ||
| 85 | if (compatibility_GameBoot->checkedId() == -1) { | ||
| 86 | button(NextButton)->setEnabled(false); | ||
| 87 | } | ||
| 88 | break; | ||
| 89 | case CompatDBPage::GamePlay: | ||
| 90 | if (compatibility_Gameplay->checkedId() == -1) { | ||
| 91 | button(NextButton)->setEnabled(false); | ||
| 92 | } | ||
| 93 | break; | ||
| 94 | case CompatDBPage::Freeze: | ||
| 95 | if (compatibility_NoFreeze->checkedId() == -1) { | ||
| 96 | button(NextButton)->setEnabled(false); | ||
| 97 | } | ||
| 98 | break; | ||
| 99 | case CompatDBPage::Completion: | ||
| 100 | if (compatibility_Complete->checkedId() == -1) { | ||
| 101 | button(NextButton)->setEnabled(false); | ||
| 102 | } | ||
| 103 | break; | ||
| 104 | case CompatDBPage::Graphical: | ||
| 105 | if (compatibility_Graphical->checkedId() == -1) { | ||
| 106 | button(NextButton)->setEnabled(false); | ||
| 107 | } | ||
| 108 | break; | ||
| 109 | case CompatDBPage::Audio: | ||
| 110 | if (compatibility_Audio->checkedId() == -1) { | ||
| 48 | button(NextButton)->setEnabled(false); | 111 | button(NextButton)->setEnabled(false); |
| 49 | } | 112 | } |
| 50 | break; | 113 | break; |
| 51 | case CompatDBPage::Final: | 114 | case CompatDBPage::Final: |
| 52 | back(); | 115 | back(); |
| 53 | LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId()); | 116 | LOG_INFO(Frontend, "Compatibility Rating: {}", compatiblity); |
| 54 | telemetry_session.AddField(Common::Telemetry::FieldType::UserFeedback, "Compatibility", | 117 | telemetry_session.AddField(Common::Telemetry::FieldType::UserFeedback, "Compatibility", |
| 55 | compatibility->checkedId()); | 118 | compatiblity); |
| 56 | 119 | ||
| 57 | button(NextButton)->setEnabled(false); | 120 | button(NextButton)->setEnabled(false); |
| 58 | button(NextButton)->setText(tr("Submitting")); | 121 | button(NextButton)->setText(tr("Submitting")); |
| @@ -67,6 +130,66 @@ void CompatDB::Submit() { | |||
| 67 | } | 130 | } |
| 68 | } | 131 | } |
| 69 | 132 | ||
| 133 | int CompatDB::nextId() const { | ||
| 134 | switch ((static_cast<CompatDBPage>(currentId()))) { | ||
| 135 | case CompatDBPage::Intro: | ||
| 136 | return static_cast<int>(CompatDBPage::GameBoot); | ||
| 137 | case CompatDBPage::GameBoot: | ||
| 138 | if (ui->radioButton_GameBoot_No->isChecked()) { | ||
| 139 | return static_cast<int>(CompatDBPage::Final); | ||
| 140 | } | ||
| 141 | return static_cast<int>(CompatDBPage::GamePlay); | ||
| 142 | case CompatDBPage::GamePlay: | ||
| 143 | if (ui->radioButton_Gameplay_No->isChecked()) { | ||
| 144 | return static_cast<int>(CompatDBPage::Final); | ||
| 145 | } | ||
| 146 | return static_cast<int>(CompatDBPage::Freeze); | ||
| 147 | case CompatDBPage::Freeze: | ||
| 148 | if (ui->radioButton_NoFreeze_No->isChecked()) { | ||
| 149 | return static_cast<int>(CompatDBPage::Final); | ||
| 150 | } | ||
| 151 | return static_cast<int>(CompatDBPage::Completion); | ||
| 152 | case CompatDBPage::Completion: | ||
| 153 | if (ui->radioButton_Complete_No->isChecked()) { | ||
| 154 | return static_cast<int>(CompatDBPage::Final); | ||
| 155 | } | ||
| 156 | return static_cast<int>(CompatDBPage::Graphical); | ||
| 157 | case CompatDBPage::Graphical: | ||
| 158 | return static_cast<int>(CompatDBPage::Audio); | ||
| 159 | case CompatDBPage::Audio: | ||
| 160 | return static_cast<int>(CompatDBPage::Final); | ||
| 161 | case CompatDBPage::Final: | ||
| 162 | return -1; | ||
| 163 | default: | ||
| 164 | LOG_ERROR(Frontend, "Unexpected page: {}", currentId()); | ||
| 165 | return static_cast<int>(CompatDBPage::Intro); | ||
| 166 | } | ||
| 167 | } | ||
| 168 | |||
| 169 | CompatibilityStatus CompatDB::CalculateCompatibility() const { | ||
| 170 | if (ui->radioButton_GameBoot_No->isChecked()) { | ||
| 171 | return CompatibilityStatus::WontBoot; | ||
| 172 | } | ||
| 173 | |||
| 174 | if (ui->radioButton_Gameplay_No->isChecked()) { | ||
| 175 | return CompatibilityStatus::IntroMenu; | ||
| 176 | } | ||
| 177 | |||
| 178 | if (ui->radioButton_NoFreeze_No->isChecked() || ui->radioButton_Complete_No->isChecked()) { | ||
| 179 | return CompatibilityStatus::Ingame; | ||
| 180 | } | ||
| 181 | |||
| 182 | if (ui->radioButton_Graphical_Major->isChecked() || ui->radioButton_Audio_Major->isChecked()) { | ||
| 183 | return CompatibilityStatus::Ingame; | ||
| 184 | } | ||
| 185 | |||
| 186 | if (ui->radioButton_Graphical_Minor->isChecked() || ui->radioButton_Audio_Minor->isChecked()) { | ||
| 187 | return CompatibilityStatus::Playable; | ||
| 188 | } | ||
| 189 | |||
| 190 | return CompatibilityStatus::Perfect; | ||
| 191 | } | ||
| 192 | |||
| 70 | void CompatDB::OnTestcaseSubmitted() { | 193 | void CompatDB::OnTestcaseSubmitted() { |
| 71 | if (!testcase_watcher.result()) { | 194 | if (!testcase_watcher.result()) { |
| 72 | QMessageBox::critical(this, tr("Communication error"), | 195 | QMessageBox::critical(this, tr("Communication error"), |
diff --git a/src/yuzu/compatdb.h b/src/yuzu/compatdb.h index 3252fc47a..37e11278b 100644 --- a/src/yuzu/compatdb.h +++ b/src/yuzu/compatdb.h | |||
| @@ -12,12 +12,22 @@ namespace Ui { | |||
| 12 | class CompatDB; | 12 | class CompatDB; |
| 13 | } | 13 | } |
| 14 | 14 | ||
| 15 | enum class CompatibilityStatus { | ||
| 16 | Perfect = 0, | ||
| 17 | Playable = 1, | ||
| 18 | // Unused: Okay = 2, | ||
| 19 | Ingame = 3, | ||
| 20 | IntroMenu = 4, | ||
| 21 | WontBoot = 5, | ||
| 22 | }; | ||
| 23 | |||
| 15 | class CompatDB : public QWizard { | 24 | class CompatDB : public QWizard { |
| 16 | Q_OBJECT | 25 | Q_OBJECT |
| 17 | 26 | ||
| 18 | public: | 27 | public: |
| 19 | explicit CompatDB(Core::TelemetrySession& telemetry_session_, QWidget* parent = nullptr); | 28 | explicit CompatDB(Core::TelemetrySession& telemetry_session_, QWidget* parent = nullptr); |
| 20 | ~CompatDB(); | 29 | ~CompatDB(); |
| 30 | int nextId() const override; | ||
| 21 | 31 | ||
| 22 | private: | 32 | private: |
| 23 | QFutureWatcher<bool> testcase_watcher; | 33 | QFutureWatcher<bool> testcase_watcher; |
| @@ -25,6 +35,7 @@ private: | |||
| 25 | std::unique_ptr<Ui::CompatDB> ui; | 35 | std::unique_ptr<Ui::CompatDB> ui; |
| 26 | 36 | ||
| 27 | void Submit(); | 37 | void Submit(); |
| 38 | CompatibilityStatus CalculateCompatibility() const; | ||
| 28 | void OnTestcaseSubmitted(); | 39 | void OnTestcaseSubmitted(); |
| 29 | void EnableNext(); | 40 | void EnableNext(); |
| 30 | 41 | ||
diff --git a/src/yuzu/compatdb.ui b/src/yuzu/compatdb.ui index 3ca55eda6..d11669df2 100644 --- a/src/yuzu/compatdb.ui +++ b/src/yuzu/compatdb.ui | |||
| @@ -58,128 +58,311 @@ | |||
| 58 | </item> | 58 | </item> |
| 59 | </layout> | 59 | </layout> |
| 60 | </widget> | 60 | </widget> |
| 61 | <widget class="QWizardPage" name="wizard_Report"> | 61 | <widget class="QWizardPage" name="wizard_GameBoot"> |
| 62 | <property name="title"> | 62 | <property name="title"> |
| 63 | <string>Report Game Compatibility</string> | 63 | <string>Report Game Compatibility</string> |
| 64 | </property> | 64 | </property> |
| 65 | <attribute name="pageId"> | 65 | <attribute name="pageId"> |
| 66 | <string notr="true">1</string> | 66 | <string notr="true">1</string> |
| 67 | </attribute> | 67 | </attribute> |
| 68 | <layout class="QFormLayout" name="formLayout"> | 68 | <layout class="QFormLayout" name="formLayout1"> |
| 69 | <item row="0" column="0" colspan="2"> | ||
| 70 | <widget class="QLabel" name="lbl_Independent1"> | ||
| 71 | <property name="font"> | ||
| 72 | <font> | ||
| 73 | <pointsize>10</pointsize> | ||
| 74 | </font> | ||
| 75 | </property> | ||
| 76 | <property name="text"> | ||
| 77 | <string><html><head/><body><p>Does the game boot?</p></body></html></string> | ||
| 78 | </property> | ||
| 79 | <property name="wordWrap"> | ||
| 80 | <bool>true</bool> | ||
| 81 | </property> | ||
| 82 | </widget> | ||
| 83 | </item> | ||
| 84 | <item row="1" column="0" colspan="2"> | ||
| 85 | <spacer name="verticalSpacer1"> | ||
| 86 | <property name="orientation"> | ||
| 87 | <enum>Qt::Vertical</enum> | ||
| 88 | </property> | ||
| 89 | <property name="sizeHint" stdset="0"> | ||
| 90 | <size> | ||
| 91 | <width>20</width> | ||
| 92 | <height>0</height> | ||
| 93 | </size> | ||
| 94 | </property> | ||
| 95 | </spacer> | ||
| 96 | </item> | ||
| 69 | <item row="2" column="0"> | 97 | <item row="2" column="0"> |
| 70 | <widget class="QRadioButton" name="radioButton_Perfect"> | 98 | <widget class="QRadioButton" name="radioButton_GameBoot_Yes"> |
| 71 | <property name="text"> | 99 | <property name="text"> |
| 72 | <string>Perfect</string> | 100 | <string>Yes The game starts to output video or audio</string> |
| 73 | </property> | 101 | </property> |
| 74 | </widget> | 102 | </widget> |
| 75 | </item> | 103 | </item> |
| 76 | <item row="2" column="1"> | 104 | <item row="4" column="0"> |
| 77 | <widget class="QLabel" name="lbl_Perfect"> | 105 | <widget class="QRadioButton" name="radioButton_GameBoot_No"> |
| 78 | <property name="text"> | 106 | <property name="text"> |
| 79 | <string><html><head/><body><p>Game functions flawlessly with no audio or graphical glitches.</p></body></html></string> | 107 | <string>No The game doesn't get past the "Launching..." screen</string> |
| 80 | </property> | 108 | </property> |
| 81 | <property name="wordWrap"> | 109 | </widget> |
| 82 | <bool>true</bool> | 110 | </item> |
| 111 | </layout> | ||
| 112 | </widget> | ||
| 113 | <widget class="QWizardPage" name="wizard_GamePlay"> | ||
| 114 | <property name="title"> | ||
| 115 | <string>Report Game Compatibility</string> | ||
| 116 | </property> | ||
| 117 | <attribute name="pageId"> | ||
| 118 | <string notr="true">2</string> | ||
| 119 | </attribute> | ||
| 120 | <layout class="QFormLayout" name="formLayout2"> | ||
| 121 | <item row="2" column="0"> | ||
| 122 | <widget class="QRadioButton" name="radioButton_Gameplay_Yes"> | ||
| 123 | <property name="text"> | ||
| 124 | <string>Yes The game gets past the intro/menu and into gameplay</string> | ||
| 83 | </property> | 125 | </property> |
| 84 | </widget> | 126 | </widget> |
| 85 | </item> | 127 | </item> |
| 86 | <item row="4" column="0"> | 128 | <item row="4" column="0"> |
| 87 | <widget class="QRadioButton" name="radioButton_Great"> | 129 | <widget class="QRadioButton" name="radioButton_Gameplay_No"> |
| 88 | <property name="text"> | 130 | <property name="text"> |
| 89 | <string>Great</string> | 131 | <string>No The game crashes or freezes while loading or using the menu</string> |
| 90 | </property> | 132 | </property> |
| 91 | </widget> | 133 | </widget> |
| 92 | </item> | 134 | </item> |
| 93 | <item row="4" column="1"> | 135 | <item row="0" column="0" colspan="2"> |
| 94 | <widget class="QLabel" name="lbl_Great"> | 136 | <widget class="QLabel" name="lbl_Independent2"> |
| 137 | <property name="font"> | ||
| 138 | <font> | ||
| 139 | <pointsize>10</pointsize> | ||
| 140 | </font> | ||
| 141 | </property> | ||
| 95 | <property name="text"> | 142 | <property name="text"> |
| 96 | <string><html><head/><body><p>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.</p></body></html></string> | 143 | <string><html><head/><body><p>Does the game reach gameplay?</p></body></html></string> |
| 97 | </property> | 144 | </property> |
| 98 | <property name="wordWrap"> | 145 | <property name="wordWrap"> |
| 99 | <bool>true</bool> | 146 | <bool>true</bool> |
| 100 | </property> | 147 | </property> |
| 101 | </widget> | 148 | </widget> |
| 102 | </item> | 149 | </item> |
| 103 | <item row="5" column="0"> | 150 | <item row="1" column="0" colspan="2"> |
| 104 | <widget class="QRadioButton" name="radioButton_Okay"> | 151 | <spacer name="verticalSpacer2"> |
| 152 | <property name="orientation"> | ||
| 153 | <enum>Qt::Vertical</enum> | ||
| 154 | </property> | ||
| 155 | <property name="sizeHint" stdset="0"> | ||
| 156 | <size> | ||
| 157 | <width>20</width> | ||
| 158 | <height>0</height> | ||
| 159 | </size> | ||
| 160 | </property> | ||
| 161 | </spacer> | ||
| 162 | </item> | ||
| 163 | </layout> | ||
| 164 | </widget> | ||
| 165 | <widget class="QWizardPage" name="wizard_NoFreeze"> | ||
| 166 | <property name="title"> | ||
| 167 | <string>Report Game Compatibility</string> | ||
| 168 | </property> | ||
| 169 | <attribute name="pageId"> | ||
| 170 | <string notr="true">3</string> | ||
| 171 | </attribute> | ||
| 172 | <layout class="QFormLayout" name="formLayout3"> | ||
| 173 | <item row="2" column="0"> | ||
| 174 | <widget class="QRadioButton" name="radioButton_NoFreeze_Yes"> | ||
| 105 | <property name="text"> | 175 | <property name="text"> |
| 106 | <string>Okay</string> | 176 | <string>Yes The game works without crashes</string> |
| 107 | </property> | 177 | </property> |
| 108 | </widget> | 178 | </widget> |
| 109 | </item> | 179 | </item> |
| 110 | <item row="5" column="1"> | 180 | <item row="4" column="0"> |
| 111 | <widget class="QLabel" name="lbl_Okay"> | 181 | <widget class="QRadioButton" name="radioButton_NoFreeze_No"> |
| 182 | <property name="text"> | ||
| 183 | <string>No The game crashes or freezes during gameplay</string> | ||
| 184 | </property> | ||
| 185 | </widget> | ||
| 186 | </item> | ||
| 187 | <item row="0" column="0" colspan="2"> | ||
| 188 | <widget class="QLabel" name="lbl_Independent3"> | ||
| 189 | <property name="font"> | ||
| 190 | <font> | ||
| 191 | <pointsize>10</pointsize> | ||
| 192 | </font> | ||
| 193 | </property> | ||
| 112 | <property name="text"> | 194 | <property name="text"> |
| 113 | <string><html><head/><body><p>Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.</p></body></html></string> | 195 | <string><html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html></string> |
| 114 | </property> | 196 | </property> |
| 115 | <property name="wordWrap"> | 197 | <property name="wordWrap"> |
| 116 | <bool>true</bool> | 198 | <bool>true</bool> |
| 117 | </property> | 199 | </property> |
| 118 | </widget> | 200 | </widget> |
| 119 | </item> | 201 | </item> |
| 120 | <item row="6" column="0"> | 202 | <item row="1" column="0" colspan="2"> |
| 121 | <widget class="QRadioButton" name="radioButton_Bad"> | 203 | <spacer name="verticalSpacer3"> |
| 204 | <property name="orientation"> | ||
| 205 | <enum>Qt::Vertical</enum> | ||
| 206 | </property> | ||
| 207 | <property name="sizeHint" stdset="0"> | ||
| 208 | <size> | ||
| 209 | <width>20</width> | ||
| 210 | <height>0</height> | ||
| 211 | </size> | ||
| 212 | </property> | ||
| 213 | </spacer> | ||
| 214 | </item> | ||
| 215 | </layout> | ||
| 216 | </widget> | ||
| 217 | <widget class="QWizardPage" name="wizard_Complete"> | ||
| 218 | <property name="title"> | ||
| 219 | <string>Report Game Compatibility</string> | ||
| 220 | </property> | ||
| 221 | <attribute name="pageId"> | ||
| 222 | <string notr="true">4</string> | ||
| 223 | </attribute> | ||
| 224 | <layout class="QFormLayout" name="formLayout4"> | ||
| 225 | <item row="2" column="0"> | ||
| 226 | <widget class="QRadioButton" name="radioButton_Complete_Yes"> | ||
| 122 | <property name="text"> | 227 | <property name="text"> |
| 123 | <string>Bad</string> | 228 | <string>Yes The game can be finished without any workarounds</string> |
| 124 | </property> | 229 | </property> |
| 125 | </widget> | 230 | </widget> |
| 126 | </item> | 231 | </item> |
| 127 | <item row="6" column="1"> | 232 | <item row="4" column="0"> |
| 128 | <widget class="QLabel" name="lbl_Bad"> | 233 | <widget class="QRadioButton" name="radioButton_Complete_No"> |
| 234 | <property name="text"> | ||
| 235 | <string>No The game can't progress past a certain area</string> | ||
| 236 | </property> | ||
| 237 | </widget> | ||
| 238 | </item> | ||
| 239 | <item row="0" column="0" colspan="2"> | ||
| 240 | <widget class="QLabel" name="lbl_Independent4"> | ||
| 241 | <property name="font"> | ||
| 242 | <font> | ||
| 243 | <pointsize>10</pointsize> | ||
| 244 | </font> | ||
| 245 | </property> | ||
| 129 | <property name="text"> | 246 | <property name="text"> |
| 130 | <string><html><head/><body><p>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.</p></body></html></string> | 247 | <string><html><head/><body><p>Is the game completely playable from start to finish?</p></body></html></string> |
| 131 | </property> | 248 | </property> |
| 132 | <property name="wordWrap"> | 249 | <property name="wordWrap"> |
| 133 | <bool>true</bool> | 250 | <bool>true</bool> |
| 134 | </property> | 251 | </property> |
| 135 | </widget> | 252 | </widget> |
| 136 | </item> | 253 | </item> |
| 137 | <item row="7" column="0"> | 254 | <item row="1" column="0" colspan="2"> |
| 138 | <widget class="QRadioButton" name="radioButton_IntroMenu"> | 255 | <spacer name="verticalSpacer4"> |
| 256 | <property name="orientation"> | ||
| 257 | <enum>Qt::Vertical</enum> | ||
| 258 | </property> | ||
| 259 | <property name="sizeHint" stdset="0"> | ||
| 260 | <size> | ||
| 261 | <width>20</width> | ||
| 262 | <height>0</height> | ||
| 263 | </size> | ||
| 264 | </property> | ||
| 265 | </spacer> | ||
| 266 | </item> | ||
| 267 | </layout> | ||
| 268 | </widget> | ||
| 269 | <widget class="QWizardPage" name="wizard_Graphical"> | ||
| 270 | <property name="title"> | ||
| 271 | <string>Report Game Compatibility</string> | ||
| 272 | </property> | ||
| 273 | <attribute name="pageId"> | ||
| 274 | <string notr="true">5</string> | ||
| 275 | </attribute> | ||
| 276 | <layout class="QFormLayout" name="formLayout5"> | ||
| 277 | <item row="2" column="0"> | ||
| 278 | <widget class="QRadioButton" name="radioButton_Graphical_Major"> | ||
| 279 | <property name="text"> | ||
| 280 | <string>Major The game has major graphical errors</string> | ||
| 281 | </property> | ||
| 282 | </widget> | ||
| 283 | </item> | ||
| 284 | <item row="4" column="0"> | ||
| 285 | <widget class="QRadioButton" name="radioButton_Graphical_Minor"> | ||
| 139 | <property name="text"> | 286 | <property name="text"> |
| 140 | <string>Intro/Menu</string> | 287 | <string>Minor The game has minor graphical errors</string> |
| 141 | </property> | 288 | </property> |
| 142 | </widget> | 289 | </widget> |
| 143 | </item> | 290 | </item> |
| 144 | <item row="7" column="1"> | 291 | <item row="6" column="0"> |
| 145 | <widget class="QLabel" name="lbl_IntroMenu"> | 292 | <widget class="QRadioButton" name="radioButton_Graphical_No"> |
| 293 | <property name="text"> | ||
| 294 | <string>None Everything is rendered as it looks on the Nintendo Switch</string> | ||
| 295 | </property> | ||
| 296 | </widget> | ||
| 297 | </item> | ||
| 298 | <item row="0" column="0" colspan="2"> | ||
| 299 | <widget class="QLabel" name="lbl_Independent5"> | ||
| 300 | <property name="font"> | ||
| 301 | <font> | ||
| 302 | <pointsize>10</pointsize> | ||
| 303 | </font> | ||
| 304 | </property> | ||
| 146 | <property name="text"> | 305 | <property name="text"> |
| 147 | <string><html><head/><body><p>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.</p></body></html></string> | 306 | <string><html><head/><body><p>Does the game have any graphical glitches?</p></body></html></string> |
| 148 | </property> | 307 | </property> |
| 149 | <property name="wordWrap"> | 308 | <property name="wordWrap"> |
| 150 | <bool>true</bool> | 309 | <bool>true</bool> |
| 151 | </property> | 310 | </property> |
| 152 | </widget> | 311 | </widget> |
| 153 | </item> | 312 | </item> |
| 154 | <item row="8" column="0"> | 313 | <item row="1" column="0" colspan="2"> |
| 155 | <widget class="QRadioButton" name="radioButton_WontBoot"> | 314 | <spacer name="verticalSpacer5"> |
| 156 | <property name="text"> | 315 | <property name="orientation"> |
| 157 | <string>Won't Boot</string> | 316 | <enum>Qt::Vertical</enum> |
| 158 | </property> | 317 | </property> |
| 159 | <property name="checkable"> | 318 | <property name="sizeHint" stdset="0"> |
| 160 | <bool>true</bool> | 319 | <size> |
| 320 | <width>20</width> | ||
| 321 | <height>0</height> | ||
| 322 | </size> | ||
| 161 | </property> | 323 | </property> |
| 162 | <property name="checked"> | 324 | </spacer> |
| 163 | <bool>false</bool> | 325 | </item> |
| 326 | </layout> | ||
| 327 | </widget> | ||
| 328 | <widget class="QWizardPage" name="wizard_Audio"> | ||
| 329 | <property name="title"> | ||
| 330 | <string>Report Game Compatibility</string> | ||
| 331 | </property> | ||
| 332 | <attribute name="pageId"> | ||
| 333 | <string notr="true">6</string> | ||
| 334 | </attribute> | ||
| 335 | <layout class="QFormLayout" name="formLayout6"> | ||
| 336 | <item row="2" column="0"> | ||
| 337 | <widget class="QRadioButton" name="radioButton_Audio_Major"> | ||
| 338 | <property name="text"> | ||
| 339 | <string>Major The game has major audio errors</string> | ||
| 340 | </property> | ||
| 341 | </widget> | ||
| 342 | </item> | ||
| 343 | <item row="4" column="0"> | ||
| 344 | <widget class="QRadioButton" name="radioButton_Audio_Minor"> | ||
| 345 | <property name="text"> | ||
| 346 | <string>Minor The game has minor audio errors</string> | ||
| 164 | </property> | 347 | </property> |
| 165 | </widget> | 348 | </widget> |
| 166 | </item> | 349 | </item> |
| 167 | <item row="8" column="1"> | 350 | <item row="6" column="0"> |
| 168 | <widget class="QLabel" name="lbl_WontBoot"> | 351 | <widget class="QRadioButton" name="radioButton_Audio_No"> |
| 169 | <property name="text"> | 352 | <property name="text"> |
| 170 | <string><html><head/><body><p>The game crashes when attempting to startup.</p></body></html></string> | 353 | <string>None Audio is played perfectly</string> |
| 171 | </property> | 354 | </property> |
| 172 | </widget> | 355 | </widget> |
| 173 | </item> | 356 | </item> |
| 174 | <item row="0" column="0" colspan="2"> | 357 | <item row="0" column="0" colspan="2"> |
| 175 | <widget class="QLabel" name="lbl_Independent"> | 358 | <widget class="QLabel" name="lbl_Independent6"> |
| 176 | <property name="font"> | 359 | <property name="font"> |
| 177 | <font> | 360 | <font> |
| 178 | <pointsize>10</pointsize> | 361 | <pointsize>10</pointsize> |
| 179 | </font> | 362 | </font> |
| 180 | </property> | 363 | </property> |
| 181 | <property name="text"> | 364 | <property name="text"> |
| 182 | <string><html><head/><body><p>Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?</p></body></html></string> | 365 | <string><html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html></string> |
| 183 | </property> | 366 | </property> |
| 184 | <property name="wordWrap"> | 367 | <property name="wordWrap"> |
| 185 | <bool>true</bool> | 368 | <bool>true</bool> |
| @@ -187,7 +370,7 @@ | |||
| 187 | </widget> | 370 | </widget> |
| 188 | </item> | 371 | </item> |
| 189 | <item row="1" column="0" colspan="2"> | 372 | <item row="1" column="0" colspan="2"> |
| 190 | <spacer name="verticalSpacer"> | 373 | <spacer name="verticalSpacer6"> |
| 191 | <property name="orientation"> | 374 | <property name="orientation"> |
| 192 | <enum>Qt::Vertical</enum> | 375 | <enum>Qt::Vertical</enum> |
| 193 | </property> | 376 | </property> |
| @@ -206,7 +389,7 @@ | |||
| 206 | <string>Thank you for your submission!</string> | 389 | <string>Thank you for your submission!</string> |
| 207 | </property> | 390 | </property> |
| 208 | <attribute name="pageId"> | 391 | <attribute name="pageId"> |
| 209 | <string notr="true">2</string> | 392 | <string notr="true">7</string> |
| 210 | </attribute> | 393 | </attribute> |
| 211 | </widget> | 394 | </widget> |
| 212 | </widget> | 395 | </widget> |
diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp index 5c0217ba8..a47089988 100644 --- a/src/yuzu/configuration/configure_profile_manager.cpp +++ b/src/yuzu/configuration/configure_profile_manager.cpp | |||
| @@ -2,6 +2,9 @@ | |||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include <algorithm> | 4 | #include <algorithm> |
| 5 | #include <functional> | ||
| 6 | #include <QDialog> | ||
| 7 | #include <QDialogButtonBox> | ||
| 5 | #include <QFileDialog> | 8 | #include <QFileDialog> |
| 6 | #include <QGraphicsItem> | 9 | #include <QGraphicsItem> |
| 7 | #include <QHeaderView> | 10 | #include <QHeaderView> |
| @@ -108,9 +111,12 @@ ConfigureProfileManager::ConfigureProfileManager(const Core::System& system_, QW | |||
| 108 | 111 | ||
| 109 | connect(ui->pm_add, &QPushButton::clicked, this, &ConfigureProfileManager::AddUser); | 112 | connect(ui->pm_add, &QPushButton::clicked, this, &ConfigureProfileManager::AddUser); |
| 110 | connect(ui->pm_rename, &QPushButton::clicked, this, &ConfigureProfileManager::RenameUser); | 113 | connect(ui->pm_rename, &QPushButton::clicked, this, &ConfigureProfileManager::RenameUser); |
| 111 | connect(ui->pm_remove, &QPushButton::clicked, this, &ConfigureProfileManager::DeleteUser); | 114 | connect(ui->pm_remove, &QPushButton::clicked, this, |
| 115 | &ConfigureProfileManager::ConfirmDeleteUser); | ||
| 112 | connect(ui->pm_set_image, &QPushButton::clicked, this, &ConfigureProfileManager::SetUserImage); | 116 | connect(ui->pm_set_image, &QPushButton::clicked, this, &ConfigureProfileManager::SetUserImage); |
| 113 | 117 | ||
| 118 | confirm_dialog = new ConfigureProfileManagerDeleteDialog(this); | ||
| 119 | |||
| 114 | scene = new QGraphicsScene; | 120 | scene = new QGraphicsScene; |
| 115 | ui->current_user_icon->setScene(scene); | 121 | ui->current_user_icon->setScene(scene); |
| 116 | 122 | ||
| @@ -230,26 +236,23 @@ void ConfigureProfileManager::RenameUser() { | |||
| 230 | UpdateCurrentUser(); | 236 | UpdateCurrentUser(); |
| 231 | } | 237 | } |
| 232 | 238 | ||
| 233 | void ConfigureProfileManager::DeleteUser() { | 239 | void ConfigureProfileManager::ConfirmDeleteUser() { |
| 234 | const auto index = tree_view->currentIndex().row(); | 240 | const auto index = tree_view->currentIndex().row(); |
| 235 | const auto uuid = profile_manager->GetUser(index); | 241 | const auto uuid = profile_manager->GetUser(index); |
| 236 | ASSERT(uuid); | 242 | ASSERT(uuid); |
| 237 | const auto username = GetAccountUsername(*profile_manager, *uuid); | 243 | const auto username = GetAccountUsername(*profile_manager, *uuid); |
| 238 | 244 | ||
| 239 | const auto confirm = QMessageBox::question( | 245 | confirm_dialog->SetInfo(username, *uuid, [this, uuid]() { DeleteUser(*uuid); }); |
| 240 | this, tr("Confirm Delete"), | 246 | confirm_dialog->show(); |
| 241 | tr("You are about to delete user with name \"%1\". Are you sure?").arg(username)); | 247 | } |
| 242 | |||
| 243 | if (confirm == QMessageBox::No) { | ||
| 244 | return; | ||
| 245 | } | ||
| 246 | 248 | ||
| 249 | void ConfigureProfileManager::DeleteUser(const Common::UUID& uuid) { | ||
| 247 | if (Settings::values.current_user.GetValue() == tree_view->currentIndex().row()) { | 250 | if (Settings::values.current_user.GetValue() == tree_view->currentIndex().row()) { |
| 248 | Settings::values.current_user = 0; | 251 | Settings::values.current_user = 0; |
| 249 | } | 252 | } |
| 250 | UpdateCurrentUser(); | 253 | UpdateCurrentUser(); |
| 251 | 254 | ||
| 252 | if (!profile_manager->RemoveUser(*uuid)) { | 255 | if (!profile_manager->RemoveUser(uuid)) { |
| 253 | return; | 256 | return; |
| 254 | } | 257 | } |
| 255 | 258 | ||
| @@ -319,3 +322,47 @@ void ConfigureProfileManager::SetUserImage() { | |||
| 319 | new QStandardItem{GetIcon(*uuid), FormatUserEntryText(username, *uuid)}); | 322 | new QStandardItem{GetIcon(*uuid), FormatUserEntryText(username, *uuid)}); |
| 320 | UpdateCurrentUser(); | 323 | UpdateCurrentUser(); |
| 321 | } | 324 | } |
| 325 | |||
| 326 | ConfigureProfileManagerDeleteDialog::ConfigureProfileManagerDeleteDialog(QWidget* parent) | ||
| 327 | : QDialog{parent} { | ||
| 328 | auto dialog_vbox_layout = new QVBoxLayout(this); | ||
| 329 | dialog_button_box = | ||
| 330 | new QDialogButtonBox(QDialogButtonBox::Yes | QDialogButtonBox::No, Qt::Horizontal, parent); | ||
| 331 | auto label_message = | ||
| 332 | new QLabel(tr("Delete this user? All of the user's save data will be deleted."), this); | ||
| 333 | label_info = new QLabel(this); | ||
| 334 | auto dialog_hbox_layout_widget = new QWidget(this); | ||
| 335 | auto dialog_hbox_layout = new QHBoxLayout(dialog_hbox_layout_widget); | ||
| 336 | icon_scene = new QGraphicsScene(0, 0, 64, 64, this); | ||
| 337 | auto icon_view = new QGraphicsView(icon_scene, this); | ||
| 338 | |||
| 339 | dialog_hbox_layout_widget->setLayout(dialog_hbox_layout); | ||
| 340 | icon_view->setMaximumSize(64, 64); | ||
| 341 | icon_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); | ||
| 342 | icon_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); | ||
| 343 | this->setLayout(dialog_vbox_layout); | ||
| 344 | this->setWindowTitle(tr("Confirm Delete")); | ||
| 345 | this->setSizeGripEnabled(false); | ||
| 346 | dialog_vbox_layout->addWidget(label_message); | ||
| 347 | dialog_vbox_layout->addWidget(dialog_hbox_layout_widget); | ||
| 348 | dialog_vbox_layout->addWidget(dialog_button_box); | ||
| 349 | dialog_hbox_layout->addWidget(icon_view); | ||
| 350 | dialog_hbox_layout->addWidget(label_info); | ||
| 351 | |||
| 352 | connect(dialog_button_box, &QDialogButtonBox::rejected, this, [this]() { close(); }); | ||
| 353 | } | ||
| 354 | |||
| 355 | ConfigureProfileManagerDeleteDialog::~ConfigureProfileManagerDeleteDialog() = default; | ||
| 356 | |||
| 357 | void ConfigureProfileManagerDeleteDialog::SetInfo(const QString& username, const Common::UUID& uuid, | ||
| 358 | std::function<void()> accept_callback) { | ||
| 359 | label_info->setText( | ||
| 360 | tr("Name: %1\nUUID: %2").arg(username, QString::fromStdString(uuid.FormattedString()))); | ||
| 361 | icon_scene->clear(); | ||
| 362 | icon_scene->addPixmap(GetIcon(uuid)); | ||
| 363 | |||
| 364 | connect(dialog_button_box, &QDialogButtonBox::accepted, this, [this, accept_callback]() { | ||
| 365 | close(); | ||
| 366 | accept_callback(); | ||
| 367 | }); | ||
| 368 | } | ||
diff --git a/src/yuzu/configuration/configure_profile_manager.h b/src/yuzu/configuration/configure_profile_manager.h index fe9033779..c4b1a334e 100644 --- a/src/yuzu/configuration/configure_profile_manager.h +++ b/src/yuzu/configuration/configure_profile_manager.h | |||
| @@ -3,16 +3,24 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <functional> | ||
| 6 | #include <memory> | 7 | #include <memory> |
| 7 | 8 | ||
| 9 | #include <QDialog> | ||
| 8 | #include <QList> | 10 | #include <QList> |
| 9 | #include <QWidget> | 11 | #include <QWidget> |
| 10 | 12 | ||
| 13 | namespace Common { | ||
| 14 | struct UUID; | ||
| 15 | } | ||
| 16 | |||
| 11 | namespace Core { | 17 | namespace Core { |
| 12 | class System; | 18 | class System; |
| 13 | } | 19 | } |
| 14 | 20 | ||
| 15 | class QGraphicsScene; | 21 | class QGraphicsScene; |
| 22 | class QDialogButtonBox; | ||
| 23 | class QLabel; | ||
| 16 | class QStandardItem; | 24 | class QStandardItem; |
| 17 | class QStandardItemModel; | 25 | class QStandardItemModel; |
| 18 | class QTreeView; | 26 | class QTreeView; |
| @@ -26,6 +34,20 @@ namespace Ui { | |||
| 26 | class ConfigureProfileManager; | 34 | class ConfigureProfileManager; |
| 27 | } | 35 | } |
| 28 | 36 | ||
| 37 | class ConfigureProfileManagerDeleteDialog : public QDialog { | ||
| 38 | public: | ||
| 39 | explicit ConfigureProfileManagerDeleteDialog(QWidget* parent); | ||
| 40 | ~ConfigureProfileManagerDeleteDialog(); | ||
| 41 | |||
| 42 | void SetInfo(const QString& username, const Common::UUID& uuid, | ||
| 43 | std::function<void()> accept_callback); | ||
| 44 | |||
| 45 | private: | ||
| 46 | QDialogButtonBox* dialog_button_box; | ||
| 47 | QGraphicsScene* icon_scene; | ||
| 48 | QLabel* label_info; | ||
| 49 | }; | ||
| 50 | |||
| 29 | class ConfigureProfileManager : public QWidget { | 51 | class ConfigureProfileManager : public QWidget { |
| 30 | Q_OBJECT | 52 | Q_OBJECT |
| 31 | 53 | ||
| @@ -47,7 +69,8 @@ private: | |||
| 47 | void SelectUser(const QModelIndex& index); | 69 | void SelectUser(const QModelIndex& index); |
| 48 | void AddUser(); | 70 | void AddUser(); |
| 49 | void RenameUser(); | 71 | void RenameUser(); |
| 50 | void DeleteUser(); | 72 | void ConfirmDeleteUser(); |
| 73 | void DeleteUser(const Common::UUID& uuid); | ||
| 51 | void SetUserImage(); | 74 | void SetUserImage(); |
| 52 | 75 | ||
| 53 | QVBoxLayout* layout; | 76 | QVBoxLayout* layout; |
| @@ -55,6 +78,8 @@ private: | |||
| 55 | QStandardItemModel* item_model; | 78 | QStandardItemModel* item_model; |
| 56 | QGraphicsScene* scene; | 79 | QGraphicsScene* scene; |
| 57 | 80 | ||
| 81 | ConfigureProfileManagerDeleteDialog* confirm_dialog; | ||
| 82 | |||
| 58 | std::vector<QList<QStandardItem*>> list_items; | 83 | std::vector<QList<QStandardItem*>> list_items; |
| 59 | 84 | ||
| 60 | std::unique_ptr<Ui::ConfigureProfileManager> ui; | 85 | std::unique_ptr<Ui::ConfigureProfileManager> ui; |
diff --git a/src/yuzu/configuration/configure_profile_manager.ui b/src/yuzu/configuration/configure_profile_manager.ui index cfe7478c8..bd6dea4f4 100644 --- a/src/yuzu/configuration/configure_profile_manager.ui +++ b/src/yuzu/configuration/configure_profile_manager.ui | |||
| @@ -57,6 +57,12 @@ | |||
| 57 | <height>48</height> | 57 | <height>48</height> |
| 58 | </size> | 58 | </size> |
| 59 | </property> | 59 | </property> |
| 60 | <property name="frameShape"> | ||
| 61 | <enum>QFrame::NoFrame</enum> | ||
| 62 | </property> | ||
| 63 | <property name="frameShadow"> | ||
| 64 | <enum>QFrame::Plain</enum> | ||
| 65 | </property> | ||
| 60 | <property name="verticalScrollBarPolicy"> | 66 | <property name="verticalScrollBarPolicy"> |
| 61 | <enum>Qt::ScrollBarAlwaysOff</enum> | 67 | <enum>Qt::ScrollBarAlwaysOff</enum> |
| 62 | </property> | 68 | </property> |
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index 6198d1e4e..1800f090f 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h | |||
| @@ -145,12 +145,14 @@ public: | |||
| 145 | const char* tooltip; | 145 | const char* tooltip; |
| 146 | }; | 146 | }; |
| 147 | // clang-format off | 147 | // clang-format off |
| 148 | const auto ingame_status = | ||
| 149 | CompatStatus{QStringLiteral("#f2d624"), QT_TR_NOOP("Ingame"), QT_TR_NOOP("Game starts, but crashes or major glitches prevent it from being completed.")}; | ||
| 148 | static const std::map<QString, CompatStatus> status_data = { | 150 | static const std::map<QString, CompatStatus> status_data = { |
| 149 | {QStringLiteral("0"), {QStringLiteral("#5c93ed"), QT_TR_NOOP("Perfect"), QT_TR_NOOP("Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without\nany workarounds needed.")}}, | 151 | {QStringLiteral("0"), {QStringLiteral("#5c93ed"), QT_TR_NOOP("Perfect"), QT_TR_NOOP("Game can be played without issues.")}}, |
| 150 | {QStringLiteral("1"), {QStringLiteral("#47d35c"), QT_TR_NOOP("Great"), QT_TR_NOOP("Game functions with minor graphical or audio glitches and is playable from start to finish. May require some\nworkarounds.")}}, | 152 | {QStringLiteral("1"), {QStringLiteral("#47d35c"), QT_TR_NOOP("Playable"), QT_TR_NOOP("Game functions with minor graphical or audio glitches and is playable from start to finish.")}}, |
| 151 | {QStringLiteral("2"), {QStringLiteral("#94b242"), QT_TR_NOOP("Okay"), QT_TR_NOOP("Game functions with major graphical or audio glitches, but game is playable from start to finish with\nworkarounds.")}}, | 153 | {QStringLiteral("2"), ingame_status}, |
| 152 | {QStringLiteral("3"), {QStringLiteral("#f2d624"), QT_TR_NOOP("Bad"), QT_TR_NOOP("Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches\neven with workarounds.")}}, | 154 | {QStringLiteral("3"), ingame_status}, // Fallback for the removed "Okay" category |
| 153 | {QStringLiteral("4"), {QStringLiteral("#FF0000"), QT_TR_NOOP("Intro/Menu"), QT_TR_NOOP("Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start\nScreen.")}}, | 155 | {QStringLiteral("4"), {QStringLiteral("#FF0000"), QT_TR_NOOP("Intro/Menu"), QT_TR_NOOP("Game loads, but is unable to progress past the Start Screen.")}}, |
| 154 | {QStringLiteral("5"), {QStringLiteral("#828282"), QT_TR_NOOP("Won't Boot"), QT_TR_NOOP("The game crashes when attempting to startup.")}}, | 156 | {QStringLiteral("5"), {QStringLiteral("#828282"), QT_TR_NOOP("Won't Boot"), QT_TR_NOOP("The game crashes when attempting to startup.")}}, |
| 155 | {QStringLiteral("99"), {QStringLiteral("#000000"), QT_TR_NOOP("Not Tested"), QT_TR_NOOP("The game has not yet been tested.")}}, | 157 | {QStringLiteral("99"), {QStringLiteral("#000000"), QT_TR_NOOP("Not Tested"), QT_TR_NOOP("The game has not yet been tested.")}}, |
| 156 | }; | 158 | }; |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 3c61899aa..7ee2302cc 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -361,6 +361,9 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan | |||
| 361 | } | 361 | } |
| 362 | } | 362 | } |
| 363 | LOG_INFO(Frontend, "Host CPU: {}", cpu_string); | 363 | LOG_INFO(Frontend, "Host CPU: {}", cpu_string); |
| 364 | if (std::optional<int> processor_core = Common::GetProcessorCount()) { | ||
| 365 | LOG_INFO(Frontend, "Host CPU Cores: {}", *processor_core); | ||
| 366 | } | ||
| 364 | #endif | 367 | #endif |
| 365 | LOG_INFO(Frontend, "Host CPU Threads: {}", processor_count); | 368 | LOG_INFO(Frontend, "Host CPU Threads: {}", processor_count); |
| 366 | LOG_INFO(Frontend, "Host OS: {}", PrettyProductName().toStdString()); | 369 | LOG_INFO(Frontend, "Host OS: {}", PrettyProductName().toStdString()); |
| @@ -2818,6 +2821,20 @@ void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_tex | |||
| 2818 | } | 2821 | } |
| 2819 | 2822 | ||
| 2820 | void GMainWindow::OnMenuReportCompatibility() { | 2823 | void GMainWindow::OnMenuReportCompatibility() { |
| 2824 | const auto& caps = Common::GetCPUCaps(); | ||
| 2825 | const bool has_fma = caps.fma || caps.fma4; | ||
| 2826 | const auto processor_count = std::thread::hardware_concurrency(); | ||
| 2827 | const bool has_4threads = processor_count == 0 || processor_count >= 4; | ||
| 2828 | const bool has_8gb_ram = Common::GetMemInfo().TotalPhysicalMemory >= 8_GiB; | ||
| 2829 | const bool has_broken_vulkan = UISettings::values.has_broken_vulkan; | ||
| 2830 | |||
| 2831 | if (!has_fma || !has_4threads || !has_8gb_ram || has_broken_vulkan) { | ||
| 2832 | QMessageBox::critical(this, tr("Hardware requirements not met"), | ||
| 2833 | tr("Your system does not meet the recommended hardware requirements. " | ||
| 2834 | "Compatibility reporting has been disabled.")); | ||
| 2835 | return; | ||
| 2836 | } | ||
| 2837 | |||
| 2821 | if (!Settings::values.yuzu_token.GetValue().empty() && | 2838 | if (!Settings::values.yuzu_token.GetValue().empty() && |
| 2822 | !Settings::values.yuzu_username.GetValue().empty()) { | 2839 | !Settings::values.yuzu_username.GetValue().empty()) { |
| 2823 | CompatDB compatdb{system->TelemetrySession(), this}; | 2840 | CompatDB compatdb{system->TelemetrySession(), this}; |