summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/common/x64/cpu_detect.cpp59
-rw-r--r--src/common/x64/cpu_detect.h4
-rw-r--r--src/core/hle/kernel/k_event.cpp15
-rw-r--r--src/core/hle/kernel/service_thread.cpp27
-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_dma.cpp37
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp3
-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/game_list_p.h12
-rw-r--r--src/yuzu/main.cpp20
28 files changed, 566 insertions, 126 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/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/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/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_dma.cpp b/src/video_core/engines/maxwell_dma.cpp
index 4eb7a100d..54523a4b2 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_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/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 f46fff340..b03e71248 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"));
@@ -66,6 +129,66 @@ void CompatDB::Submit() {
66 } 129 }
67} 130}
68 131
132int CompatDB::nextId() const {
133 switch ((static_cast<CompatDBPage>(currentId()))) {
134 case CompatDBPage::Intro:
135 return static_cast<int>(CompatDBPage::GameBoot);
136 case CompatDBPage::GameBoot:
137 if (ui->radioButton_GameBoot_No->isChecked()) {
138 return static_cast<int>(CompatDBPage::Final);
139 }
140 return static_cast<int>(CompatDBPage::GamePlay);
141 case CompatDBPage::GamePlay:
142 if (ui->radioButton_Gameplay_No->isChecked()) {
143 return static_cast<int>(CompatDBPage::Final);
144 }
145 return static_cast<int>(CompatDBPage::Freeze);
146 case CompatDBPage::Freeze:
147 if (ui->radioButton_NoFreeze_No->isChecked()) {
148 return static_cast<int>(CompatDBPage::Final);
149 }
150 return static_cast<int>(CompatDBPage::Completion);
151 case CompatDBPage::Completion:
152 if (ui->radioButton_Complete_No->isChecked()) {
153 return static_cast<int>(CompatDBPage::Final);
154 }
155 return static_cast<int>(CompatDBPage::Graphical);
156 case CompatDBPage::Graphical:
157 return static_cast<int>(CompatDBPage::Audio);
158 case CompatDBPage::Audio:
159 return static_cast<int>(CompatDBPage::Final);
160 case CompatDBPage::Final:
161 return -1;
162 default:
163 LOG_ERROR(Frontend, "Unexpected page: {}", currentId());
164 return static_cast<int>(CompatDBPage::Intro);
165 }
166}
167
168CompatibilityStatus CompatDB::CalculateCompatibility() const {
169 if (ui->radioButton_GameBoot_No->isChecked()) {
170 return CompatibilityStatus::WontBoot;
171 }
172
173 if (ui->radioButton_Gameplay_No->isChecked()) {
174 return CompatibilityStatus::IntroMenu;
175 }
176
177 if (ui->radioButton_NoFreeze_No->isChecked() || ui->radioButton_Complete_No->isChecked()) {
178 return CompatibilityStatus::Ingame;
179 }
180
181 if (ui->radioButton_Graphical_Major->isChecked() || ui->radioButton_Audio_Major->isChecked()) {
182 return CompatibilityStatus::Ingame;
183 }
184
185 if (ui->radioButton_Graphical_Minor->isChecked() || ui->radioButton_Audio_Minor->isChecked()) {
186 return CompatibilityStatus::Playable;
187 }
188
189 return CompatibilityStatus::Perfect;
190}
191
69void CompatDB::OnTestcaseSubmitted() { 192void CompatDB::OnTestcaseSubmitted() {
70 if (!testcase_watcher.result()) { 193 if (!testcase_watcher.result()) {
71 QMessageBox::critical(this, tr("Communication error"), 194 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/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 c27f8196d..032ff1cbc 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -342,6 +342,7 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
342 const auto override_build = 342 const auto override_build =
343 fmt::format(fmt::runtime(std::string(Common::g_title_bar_format_idle)), build_id); 343 fmt::format(fmt::runtime(std::string(Common::g_title_bar_format_idle)), build_id);
344 const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build; 344 const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build;
345 const auto processor_count = std::thread::hardware_concurrency();
345 346
346 LOG_INFO(Frontend, "yuzu Version: {}", yuzu_build_version); 347 LOG_INFO(Frontend, "yuzu Version: {}", yuzu_build_version);
347 LogRuntimes(); 348 LogRuntimes();
@@ -361,6 +362,11 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
361 } 362 }
362 LOG_INFO(Frontend, "Host CPU: {}", cpu_string); 363 LOG_INFO(Frontend, "Host CPU: {}", cpu_string);
363#endif 364#endif
365
366 if (std::optional<int> processor_core = Common::GetProcessorCount()) {
367 LOG_INFO(Frontend, "Host CPU Cores: {}", *processor_core);
368 }
369 LOG_INFO(Frontend, "Host CPU Threads: {}", processor_count);
364 LOG_INFO(Frontend, "Host OS: {}", PrettyProductName().toStdString()); 370 LOG_INFO(Frontend, "Host OS: {}", PrettyProductName().toStdString());
365 LOG_INFO(Frontend, "Host RAM: {:.2f} GiB", 371 LOG_INFO(Frontend, "Host RAM: {:.2f} GiB",
366 Common::GetMemInfo().TotalPhysicalMemory / f64{1_GiB}); 372 Common::GetMemInfo().TotalPhysicalMemory / f64{1_GiB});
@@ -2815,6 +2821,20 @@ void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_tex
2815} 2821}
2816 2822
2817void 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
2818 if (!Settings::values.yuzu_token.GetValue().empty() && 2838 if (!Settings::values.yuzu_token.GetValue().empty() &&
2819 !Settings::values.yuzu_username.GetValue().empty()) { 2839 !Settings::values.yuzu_username.GetValue().empty()) {
2820 CompatDB compatdb{system->TelemetrySession(), this}; 2840 CompatDB compatdb{system->TelemetrySession(), this};