summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/x64/cpu_detect.cpp59
-rw-r--r--src/common/x64/cpu_detect.h4
-rw-r--r--src/core/debugger/debugger.cpp161
-rw-r--r--src/core/debugger/gdbstub.cpp151
-rw-r--r--src/core/debugger/gdbstub.h1
-rw-r--r--src/core/hle/kernel/k_event.cpp15
-rw-r--r--src/core/hle/kernel/k_page_table.h3
-rw-r--r--src/core/hle/kernel/service_thread.cpp27
-rw-r--r--src/core/hle/result.h76
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp12
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_instructions.h1
-rw-r--r--src/shader_recompiler/backend/glasm/glasm_emit_context.cpp4
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp12
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_instructions.h1
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp12
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_instructions.h1
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp4
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.h1
-rw-r--r--src/shader_recompiler/frontend/ir/ir_emitter.cpp4
-rw-r--r--src/shader_recompiler/frontend/ir/ir_emitter.h1
-rw-r--r--src/shader_recompiler/frontend/ir/opcodes.inc1
-rw-r--r--src/shader_recompiler/frontend/ir/patch.h4
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp3
-rw-r--r--src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp3
-rw-r--r--src/shader_recompiler/shader_info.h1
-rw-r--r--src/video_core/engines/maxwell_3d.cpp5
-rw-r--r--src/video_core/engines/maxwell_3d.h16
-rw-r--r--src/video_core/engines/maxwell_dma.cpp37
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp2
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp3
-rw-r--r--src/video_core/renderer_vulkan/fixed_pipeline_state.cpp1
-rw-r--r--src/video_core/renderer_vulkan/fixed_pipeline_state.h1
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp12
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp1
-rw-r--r--src/yuzu/compatdb.cpp161
-rw-r--r--src/yuzu/compatdb.h11
-rw-r--r--src/yuzu/compatdb.ui277
-rw-r--r--src/yuzu/configuration/configure_profile_manager.cpp67
-rw-r--r--src/yuzu/configuration/configure_profile_manager.h27
-rw-r--r--src/yuzu/configuration/configure_profile_manager.ui6
-rw-r--r--src/yuzu/game_list_p.h12
-rw-r--r--src/yuzu/main.cpp17
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
25static 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
42static inline u64 _xgetbv(u32 index) { 55static 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
50namespace Common { 62namespace 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
207std::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 */
75const CPUCaps& GetCPUCaps(); 76const CPUCaps& GetCPUCaps();
76 77
78/// Detects CPU core count
79std::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
35template <typename Callback>
36static 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
36template <typename Readable, typename Buffer> 45template <typename Readable, typename Buffer>
37static std::span<const u8> ReceiveInto(Readable& r, Buffer& buffer) { 46static 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
60class DebuggerImpl : public DebuggerBackend { 69class DebuggerImpl : public DebuggerBackend {
61public: 70public:
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
108private: 118private:
@@ -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
291Debugger::Debugger(Core::System& system, u16 port) { 316Debugger::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
650constexpr 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
675static 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
684static 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
701static 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
727void 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
648Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) { 799Kernel::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() {
50void KEvent::PostDestroy(uintptr_t arg) { 54void 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
188ServiceThread::Impl::Impl(KernelCore& kernel_, const std::string& service_name) 184ServiceThread::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
382void 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
382void EmitSampleId(EmitContext& ctx, IR::Inst& inst) { 394void 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);
69void EmitWorkgroupId(EmitContext& ctx, IR::Inst& inst); 69void EmitWorkgroupId(EmitContext& ctx, IR::Inst& inst);
70void EmitLocalInvocationId(EmitContext& ctx, IR::Inst& inst); 70void EmitLocalInvocationId(EmitContext& ctx, IR::Inst& inst);
71void EmitInvocationId(EmitContext& ctx, IR::Inst& inst); 71void EmitInvocationId(EmitContext& ctx, IR::Inst& inst);
72void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst);
72void EmitSampleId(EmitContext& ctx, IR::Inst& inst); 73void EmitSampleId(EmitContext& ctx, IR::Inst& inst);
73void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst); 74void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst);
74void EmitYDirection(EmitContext& ctx, IR::Inst& inst); 75void 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
402void 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
402void EmitSampleId(EmitContext& ctx, IR::Inst& inst) { 414void 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);
83void EmitWorkgroupId(EmitContext& ctx, IR::Inst& inst); 83void EmitWorkgroupId(EmitContext& ctx, IR::Inst& inst);
84void EmitLocalInvocationId(EmitContext& ctx, IR::Inst& inst); 84void EmitLocalInvocationId(EmitContext& ctx, IR::Inst& inst);
85void EmitInvocationId(EmitContext& ctx, IR::Inst& inst); 85void EmitInvocationId(EmitContext& ctx, IR::Inst& inst);
86void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst);
86void EmitSampleId(EmitContext& ctx, IR::Inst& inst); 87void EmitSampleId(EmitContext& ctx, IR::Inst& inst);
87void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst); 88void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst);
88void EmitYDirection(EmitContext& ctx, IR::Inst& inst); 89void 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
515Id 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
515Id EmitSampleId(EmitContext& ctx) { 527Id 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);
72Id EmitWorkgroupId(EmitContext& ctx); 72Id EmitWorkgroupId(EmitContext& ctx);
73Id EmitLocalInvocationId(EmitContext& ctx); 73Id EmitLocalInvocationId(EmitContext& ctx);
74Id EmitInvocationId(EmitContext& ctx); 74Id EmitInvocationId(EmitContext& ctx);
75Id EmitInvocationInfo(EmitContext& ctx);
75Id EmitSampleId(EmitContext& ctx); 76Id EmitSampleId(EmitContext& ctx);
76Id EmitIsHelperInvocation(EmitContext& ctx); 77Id EmitIsHelperInvocation(EmitContext& ctx);
77Id EmitYDirection(EmitContext& ctx); 78Id 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
365U32 IREmitter::InvocationInfo() {
366 return Inst<U32>(Opcode::InvocationInfo);
367}
368
365U32 IREmitter::SampleId() { 369U32 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,
59OPCODE(WorkgroupId, U32x3, ) 59OPCODE(WorkgroupId, U32x3, )
60OPCODE(LocalInvocationId, U32x3, ) 60OPCODE(LocalInvocationId, U32x3, )
61OPCODE(InvocationId, U32, ) 61OPCODE(InvocationId, U32, )
62OPCODE(InvocationInfo, U32, )
62OPCODE(SampleId, U32, ) 63OPCODE(SampleId, U32, )
63OPCODE(IsHelperInvocation, U1, ) 64OPCODE(IsHelperInvocation, U1, )
64OPCODE(YDirection, F32, ) 65OPCODE(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};
140static_assert(static_cast<u64>(Patch::Component119) == 127); 138static_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);
3287ASSERT_REG_POSITION(stencil_back_ref, 0x0F54); 3292ASSERT_REG_POSITION(stencil_back_ref, 0x0F54);
3288ASSERT_REG_POSITION(stencil_back_mask, 0x0F58); 3293ASSERT_REG_POSITION(stencil_back_mask, 0x0F58);
3289ASSERT_REG_POSITION(stencil_back_func_mask, 0x0F5C); 3294ASSERT_REG_POSITION(stencil_back_func_mask, 0x0F5C);
3295ASSERT_REG_POSITION(invalidate_texture_data_cache, 0x0F74);
3296ASSERT_REG_POSITION(tiled_cache_barrier, 0x0F7C);
3290ASSERT_REG_POSITION(vertex_stream_substitute, 0x0F84); 3297ASSERT_REG_POSITION(vertex_stream_substitute, 0x0F84);
3291ASSERT_REG_POSITION(line_mode_clip_generated_edge_do_not_draw, 0x0F8C); 3298ASSERT_REG_POSITION(line_mode_clip_generated_edge_do_not_draw, 0x0F8C);
3292ASSERT_REG_POSITION(color_mask_common, 0x0F90); 3299ASSERT_REG_POSITION(color_mask_common, 0x0F90);
@@ -3343,6 +3350,7 @@ ASSERT_REG_POSITION(post_ps_use_pre_ps_coverage, 0x1138);
3343ASSERT_REG_POSITION(fill_via_triangle_mode, 0x113C); 3350ASSERT_REG_POSITION(fill_via_triangle_mode, 0x113C);
3344ASSERT_REG_POSITION(blend_per_format_snorm8_unorm16_snorm16_enabled, 0x1140); 3351ASSERT_REG_POSITION(blend_per_format_snorm8_unorm16_snorm16_enabled, 0x1140);
3345ASSERT_REG_POSITION(flush_pending_writes_sm_gloal_store, 0x1144); 3352ASSERT_REG_POSITION(flush_pending_writes_sm_gloal_store, 0x1144);
3353ASSERT_REG_POSITION(conservative_raster_enable, 0x1148);
3346ASSERT_REG_POSITION(vertex_attrib_format, 0x1160); 3354ASSERT_REG_POSITION(vertex_attrib_format, 0x1160);
3347ASSERT_REG_POSITION(multisample_sample_locations, 0x11E0); 3355ASSERT_REG_POSITION(multisample_sample_locations, 0x11E0);
3348ASSERT_REG_POSITION(offset_render_target_index_by_viewport_index, 0x11F0); 3356ASSERT_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
31enum class CompatDBPage { 41enum 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
37void CompatDB::Submit() { 52void 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
133int 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
169CompatibilityStatus 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
70void CompatDB::OnTestcaseSubmitted() { 193void 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 {
12class CompatDB; 12class CompatDB;
13} 13}
14 14
15enum class CompatibilityStatus {
16 Perfect = 0,
17 Playable = 1,
18 // Unused: Okay = 2,
19 Ingame = 3,
20 IntroMenu = 4,
21 WontBoot = 5,
22};
23
15class CompatDB : public QWizard { 24class CompatDB : public QWizard {
16 Q_OBJECT 25 Q_OBJECT
17 26
18public: 27public:
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
22private: 32private:
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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Does the game boot?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 107 <string>No The game doesn't get past the &quot;Launching...&quot; 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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 143 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Does the game reach gameplay?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 195 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Does the game work without crashing, freezing or locking up during gameplay?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 247 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Is the game completely playable from start to finish?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 306 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Does the game have any graphical glitches?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 365 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Does the game have any audio glitches / missing effects?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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
233void ConfigureProfileManager::DeleteUser() { 239void 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
249void 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
326ConfigureProfileManagerDeleteDialog::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
355ConfigureProfileManagerDeleteDialog::~ConfigureProfileManagerDeleteDialog() = default;
356
357void 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
13namespace Common {
14struct UUID;
15}
16
11namespace Core { 17namespace Core {
12class System; 18class System;
13} 19}
14 20
15class QGraphicsScene; 21class QGraphicsScene;
22class QDialogButtonBox;
23class QLabel;
16class QStandardItem; 24class QStandardItem;
17class QStandardItemModel; 25class QStandardItemModel;
18class QTreeView; 26class QTreeView;
@@ -26,6 +34,20 @@ namespace Ui {
26class ConfigureProfileManager; 34class ConfigureProfileManager;
27} 35}
28 36
37class ConfigureProfileManagerDeleteDialog : public QDialog {
38public:
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
45private:
46 QDialogButtonBox* dialog_button_box;
47 QGraphicsScene* icon_scene;
48 QLabel* label_info;
49};
50
29class ConfigureProfileManager : public QWidget { 51class 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
2820void GMainWindow::OnMenuReportCompatibility() { 2823void 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};