diff options
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 @@ #include <array> #include <cstring> +#include <fstream> #include <iterator> +#include <optional> #include <string_view> +#include <thread> +#include <vector> #include "common/bit_util.h" #include "common/common_types.h" +#include "common/logging/log.h" #include "common/x64/cpu_detect.h" +#ifdef _WIN32 +#include <windows.h> +#endif + #ifdef _MSC_VER #include <intrin.h> + +static inline u64 xgetbv(u32 index) { + return _xgetbv(index); +} #else #if defined(__DragonFly__) || defined(__FreeBSD__) @@ -39,12 +52,11 @@ static inline void __cpuid(int info[4], u32 function_id) { } #define _XCR_XFEATURE_ENABLED_MASK 0 -static inline u64 _xgetbv(u32 index) { +static inline u64 xgetbv(u32 index) { u32 eax, edx; __asm__ __volatile__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(index)); return ((u64)edx << 32) | eax; } - #endif // _MSC_VER namespace Common { @@ -107,7 +119,7 @@ static CPUCaps Detect() { // - Is the XSAVE bit set in CPUID? // - XGETBV result has the XCR bit set. if (Common::Bit<28>(cpu_id[2]) && Common::Bit<27>(cpu_id[2])) { - if ((_xgetbv(_XCR_XFEATURE_ENABLED_MASK) & 0x6) == 0x6) { + if ((xgetbv(_XCR_XFEATURE_ENABLED_MASK) & 0x6) == 0x6) { caps.avx = true; if (Common::Bit<12>(cpu_id[2])) caps.fma = true; @@ -192,4 +204,45 @@ const CPUCaps& GetCPUCaps() { return caps; } +std::optional<int> GetProcessorCount() { +#if defined(_WIN32) + // Get the buffer length. + DWORD length = 0; + GetLogicalProcessorInformation(nullptr, &length); + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + LOG_ERROR(Frontend, "Failed to query core count."); + return std::nullopt; + } + std::vector<SYSTEM_LOGICAL_PROCESSOR_INFORMATION> buffer( + length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)); + // Now query the core count. + if (!GetLogicalProcessorInformation(buffer.data(), &length)) { + LOG_ERROR(Frontend, "Failed to query core count."); + return std::nullopt; + } + return static_cast<int>( + std::count_if(buffer.cbegin(), buffer.cend(), [](const auto& proc_info) { + return proc_info.Relationship == RelationProcessorCore; + })); +#elif defined(__unix__) + const int thread_count = std::thread::hardware_concurrency(); + std::ifstream smt("/sys/devices/system/cpu/smt/active"); + char state = '0'; + if (smt) { + smt.read(&state, sizeof(state)); + } + switch (state) { + case '0': + return thread_count; + case '1': + return thread_count / 2; + default: + return std::nullopt; + } +#else + // Shame on you + return std::nullopt; +#endif +} + } // 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 @@ #pragma once +#include <optional> #include <string_view> #include "common/common_types.h" @@ -74,4 +75,7 @@ struct CPUCaps { */ const CPUCaps& GetCPUCaps(); +/// Detects CPU core count +std::optional<int> GetProcessorCount(); + } // 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) { m_readable_event.Initialize(this); // Set our owner process. - m_owner = owner; - m_owner->Open(); + // HACK: this should never be nullptr, but service threads don't have a + // proper parent process yet. + if (owner != nullptr) { + m_owner = owner; + m_owner->Open(); + } // Mark initialized. m_initialized = true; @@ -50,8 +54,11 @@ Result KEvent::Clear() { void KEvent::PostDestroy(uintptr_t arg) { // Release the event count resource the owner process holds. KProcess* owner = reinterpret_cast<KProcess*>(arg); - owner->GetResourceLimit()->Release(LimitableResource::EventCountMax, 1); - owner->Close(); + + if (owner != nullptr) { + owner->GetResourceLimit()->Release(LimitableResource::EventCountMax, 1); + owner->Close(); + } } } // 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: std::mutex m_session_mutex; std::map<KServerSession*, std::shared_ptr<SessionRequestManager>> m_sessions; KEvent* m_wakeup_event; - KProcess* m_process; KThread* m_thread; std::atomic<bool> m_shutdown_requested; const std::string m_service_name; @@ -180,39 +179,17 @@ ServiceThread::Impl::~Impl() { // Close thread. m_thread->Close(); - - // Close process. - m_process->Close(); } ServiceThread::Impl::Impl(KernelCore& kernel_, const std::string& service_name) : kernel{kernel_}, m_service_name{service_name} { - // Initialize process. - m_process = KProcess::Create(kernel); - KProcess::Initialize(m_process, kernel.System(), service_name, - KProcess::ProcessType::KernelInternal, kernel.GetSystemResourceLimit()); - - // Reserve a new event from the process resource limit - KScopedResourceReservation event_reservation(m_process, LimitableResource::EventCountMax); - ASSERT(event_reservation.Succeeded()); - // Initialize event. m_wakeup_event = KEvent::Create(kernel); - m_wakeup_event->Initialize(m_process); - - // Commit the event reservation. - event_reservation.Commit(); - - // Reserve a new thread from the process resource limit - KScopedResourceReservation thread_reservation(m_process, LimitableResource::ThreadCountMax); - ASSERT(thread_reservation.Succeeded()); + m_wakeup_event->Initialize(nullptr); // Initialize thread. m_thread = KThread::Create(kernel); - ASSERT(KThread::InitializeDummyThread(m_thread, m_process).IsSuccess()); - - // Commit the thread reservation. - thread_reservation.Commit(); + ASSERT(KThread::InitializeDummyThread(m_thread, nullptr).IsSuccess()); // Start thread. 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) { ctx.Add("MOV.S {}.x,primitive_invocation.x;", inst); } +void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst) { + switch (ctx.stage) { + case Stage::TessellationControl: + case Stage::TessellationEval: + ctx.Add("SHL.U {}.x,primitive.vertexcount,16;", inst); + break; + default: + LOG_WARNING(Shader, "(STUBBED) called"); + ctx.Add("MOV.S {}.x,0x00ff0000;", inst); + } +} + void EmitSampleId(EmitContext& ctx, IR::Inst& inst) { ctx.Add("MOV.S {}.x,fragment.sampleid.x;", inst); } 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); void EmitWorkgroupId(EmitContext& ctx, IR::Inst& inst); void EmitLocalInvocationId(EmitContext& ctx, IR::Inst& inst); void EmitInvocationId(EmitContext& ctx, IR::Inst& inst); +void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst); void EmitSampleId(EmitContext& ctx, IR::Inst& inst); void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst); void EmitYDirection(EmitContext& ctx, IR::Inst& inst); diff --git a/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp b/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp index 89603c1c4..333a91cc5 100644 --- a/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp +++ b/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp @@ -95,6 +95,10 @@ EmitContext::EmitContext(IR::Program& program, Bindings& bindings, const Profile if (info.uses_invocation_id) { Add("ATTRIB primitive_invocation=primitive.invocation;"); } + if (info.uses_invocation_info && + (stage == Stage::TessellationControl || stage == Stage::TessellationEval)) { + Add("ATTRIB primitive_vertexcount = primitive.vertexcount;"); + } if (info.stores_tess_level_outer) { Add("OUTPUT result_patch_tessouter[]={{result.patch.tessouter[0..3]}};"); } 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) { ctx.AddU32("{}=uint(gl_InvocationID);", inst); } +void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst) { + switch (ctx.stage) { + case Stage::TessellationControl: + case Stage::TessellationEval: + ctx.AddU32("{}=uint(gl_PatchVerticesIn)<<16;", inst); + break; + default: + LOG_WARNING(Shader, "(STUBBED) called"); + ctx.AddU32("{}=uint(0x00ff0000);", inst); + } +} + void EmitSampleId(EmitContext& ctx, IR::Inst& inst) { ctx.AddU32("{}=uint(gl_SampleID);", inst); } 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); void EmitWorkgroupId(EmitContext& ctx, IR::Inst& inst); void EmitLocalInvocationId(EmitContext& ctx, IR::Inst& inst); void EmitInvocationId(EmitContext& ctx, IR::Inst& inst); +void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst); void EmitSampleId(EmitContext& ctx, IR::Inst& inst); void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst); void EmitYDirection(EmitContext& ctx, IR::Inst& inst); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp index a4751b42d..5b3b5d1f3 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp @@ -512,6 +512,18 @@ Id EmitInvocationId(EmitContext& ctx) { return ctx.OpLoad(ctx.U32[1], ctx.invocation_id); } +Id EmitInvocationInfo(EmitContext& ctx) { + switch (ctx.stage) { + case Stage::TessellationControl: + case Stage::TessellationEval: + return ctx.OpShiftLeftLogical(ctx.U32[1], ctx.OpLoad(ctx.U32[1], ctx.patch_vertices_in), + ctx.Const(16u)); + default: + LOG_WARNING(Shader, "(STUBBED) called"); + return ctx.Const(0x00ff0000u); + } +} + Id EmitSampleId(EmitContext& ctx) { return ctx.OpLoad(ctx.U32[1], ctx.sample_id); } 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); Id EmitWorkgroupId(EmitContext& ctx); Id EmitLocalInvocationId(EmitContext& ctx); Id EmitInvocationId(EmitContext& ctx); +Id EmitInvocationInfo(EmitContext& ctx); Id EmitSampleId(EmitContext& ctx); Id EmitIsHelperInvocation(EmitContext& ctx); Id EmitYDirection(EmitContext& ctx); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index c26ad8f93..0bfc2dd89 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -1325,6 +1325,10 @@ void EmitContext::DefineInputs(const IR::Program& program) { if (info.uses_invocation_id) { invocation_id = DefineInput(*this, U32[1], false, spv::BuiltIn::InvocationId); } + if (info.uses_invocation_info && + (stage == Shader::Stage::TessellationControl || stage == Shader::Stage::TessellationEval)) { + patch_vertices_in = DefineInput(*this, U32[1], false, spv::BuiltIn::PatchVertices); + } if (info.uses_sample_id) { sample_id = DefineInput(*this, U32[1], false, spv::BuiltIn::SampleId); } 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: Id workgroup_id{}; Id local_invocation_id{}; Id invocation_id{}; + Id patch_vertices_in{}; Id sample_id{}; Id is_helper_invocation{}; 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() { return Inst<U32>(Opcode::InvocationId); } +U32 IREmitter::InvocationInfo() { + return Inst<U32>(Opcode::InvocationInfo); +} + U32 IREmitter::SampleId() { return Inst<U32>(Opcode::SampleId); } 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: [[nodiscard]] U32 LocalInvocationIdZ(); [[nodiscard]] U32 InvocationId(); + [[nodiscard]] U32 InvocationInfo(); [[nodiscard]] U32 SampleId(); [[nodiscard]] U1 IsHelperInvocation(); [[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, OPCODE(WorkgroupId, U32x3, ) OPCODE(LocalInvocationId, U32x3, ) OPCODE(InvocationId, U32, ) +OPCODE(InvocationInfo, U32, ) OPCODE(SampleId, U32, ) OPCODE(IsHelperInvocation, U1, ) OPCODE(YDirection, F32, ) diff --git a/src/shader_recompiler/frontend/ir/patch.h b/src/shader_recompiler/frontend/ir/patch.h index 1e37c8eb6..5077e56c2 100644 --- a/src/shader_recompiler/frontend/ir/patch.h +++ b/src/shader_recompiler/frontend/ir/patch.h @@ -14,8 +14,6 @@ enum class Patch : u64 { TessellationLodBottom, TessellationLodInteriorU, TessellationLodInteriorV, - ComponentPadding0, - ComponentPadding1, Component0, Component1, Component2, @@ -137,7 +135,7 @@ enum class Patch : u64 { Component118, Component119, }; -static_assert(static_cast<u64>(Patch::Component119) == 127); +static_assert(static_cast<u64>(Patch::Component119) == 125); [[nodiscard]] bool IsGeneric(Patch patch) noexcept; 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 { case SpecialRegister::SR_THREAD_KILL: return IR::U32{ir.Select(ir.IsHelperInvocation(), ir.Imm32(-1), ir.Imm32(0))}; case SpecialRegister::SR_INVOCATION_INFO: - LOG_WARNING(Shader, "(STUBBED) SR_INVOCATION_INFO"); - return ir.Imm32(0x00ff'0000); + return ir.InvocationInfo(); case SpecialRegister::SR_TID: { const IR::Value tid{ir.LocalInvocationId()}; 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) { case IR::Opcode::InvocationId: info.uses_invocation_id = true; break; + case IR::Opcode::InvocationInfo: + info.uses_invocation_info = true; + break; case IR::Opcode::SampleId: info.uses_sample_id = true; 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 { bool uses_workgroup_id{}; bool uses_local_invocation_id{}; bool uses_invocation_id{}; + bool uses_invocation_info{}; bool uses_sample_id{}; bool uses_is_helper_invocation{}; 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() { const bool is_src_pitch = IsPitchKind(static_cast<PTEKind>(src_kind)); const bool is_dst_pitch = IsPitchKind(static_cast<PTEKind>(dst_kind)); if (!is_src_pitch && is_dst_pitch) { - std::vector<u8> tmp_buffer(regs.line_length_in); - std::vector<u8> dst_buffer(regs.line_length_in); - memory_manager.ReadBlockUnsafe(regs.offset_in, tmp_buffer.data(), - regs.line_length_in); - for (u32 offset = 0; offset < regs.line_length_in; ++offset) { - dst_buffer[offset] = - tmp_buffer[convert_linear_2_blocklinear_addr(regs.offset_in + offset) - - regs.offset_in]; + UNIMPLEMENTED_IF(regs.line_length_in % 16 != 0); + UNIMPLEMENTED_IF(regs.offset_in % 16 != 0); + UNIMPLEMENTED_IF(regs.offset_out % 16 != 0); + std::vector<u8> tmp_buffer(16); + for (u32 offset = 0; offset < regs.line_length_in; offset += 16) { + memory_manager.ReadBlockUnsafe( + convert_linear_2_blocklinear_addr(regs.offset_in + offset), + tmp_buffer.data(), tmp_buffer.size()); + memory_manager.WriteBlock(regs.offset_out + offset, tmp_buffer.data(), + tmp_buffer.size()); } - memory_manager.WriteBlock(regs.offset_out, dst_buffer.data(), regs.line_length_in); } else if (is_src_pitch && !is_dst_pitch) { - std::vector<u8> tmp_buffer(regs.line_length_in); - std::vector<u8> dst_buffer(regs.line_length_in); - memory_manager.ReadBlockUnsafe(regs.offset_in, tmp_buffer.data(), - regs.line_length_in); - for (u32 offset = 0; offset < regs.line_length_in; ++offset) { - dst_buffer[convert_linear_2_blocklinear_addr(regs.offset_out + offset) - - regs.offset_out] = tmp_buffer[offset]; + UNIMPLEMENTED_IF(regs.line_length_in % 16 != 0); + UNIMPLEMENTED_IF(regs.offset_in % 16 != 0); + UNIMPLEMENTED_IF(regs.offset_out % 16 != 0); + std::vector<u8> tmp_buffer(16); + for (u32 offset = 0; offset < regs.line_length_in; offset += 16) { + memory_manager.ReadBlockUnsafe(regs.offset_in + offset, tmp_buffer.data(), + tmp_buffer.size()); + memory_manager.WriteBlock( + convert_linear_2_blocklinear_addr(regs.offset_out + offset), + tmp_buffer.data(), tmp_buffer.size()); } - memory_manager.WriteBlock(regs.offset_out, dst_buffer.data(), regs.line_length_in); } else { if (!accelerate.BufferCopy(regs.offset_in, regs.offset_out, regs.line_length_in)) { 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, } break; case Shader::Stage::TessellationEval: - info.tess_clockwise = key.tessellation_clockwise != 0; + // Flip the face, as OpenGL's drawing is flipped. + info.tess_clockwise = key.tessellation_clockwise == 0; info.tess_primitive = [&key] { switch (key.tessellation_primitive) { 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 } break; case Shader::Stage::TessellationEval: + info.tess_clockwise = key.state.tessellation_clockwise != 0; info.tess_primitive = [&key] { const u32 raw{key.state.tessellation_primitive.Value()}; 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) : QWizard(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), ui{std::make_unique<Ui::CompatDB>()}, telemetry_session{telemetry_session_} { ui->setupUi(this); - connect(ui->radioButton_Perfect, &QRadioButton::clicked, this, &CompatDB::EnableNext); - connect(ui->radioButton_Great, &QRadioButton::clicked, this, &CompatDB::EnableNext); - connect(ui->radioButton_Okay, &QRadioButton::clicked, this, &CompatDB::EnableNext); - connect(ui->radioButton_Bad, &QRadioButton::clicked, this, &CompatDB::EnableNext); - connect(ui->radioButton_IntroMenu, &QRadioButton::clicked, this, &CompatDB::EnableNext); - connect(ui->radioButton_WontBoot, &QRadioButton::clicked, this, &CompatDB::EnableNext); + + connect(ui->radioButton_GameBoot_Yes, &QRadioButton::clicked, this, &CompatDB::EnableNext); + connect(ui->radioButton_GameBoot_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); + connect(ui->radioButton_Gameplay_Yes, &QRadioButton::clicked, this, &CompatDB::EnableNext); + connect(ui->radioButton_Gameplay_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); + connect(ui->radioButton_NoFreeze_Yes, &QRadioButton::clicked, this, &CompatDB::EnableNext); + connect(ui->radioButton_NoFreeze_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); + connect(ui->radioButton_Complete_Yes, &QRadioButton::clicked, this, &CompatDB::EnableNext); + connect(ui->radioButton_Complete_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); + connect(ui->radioButton_Graphical_Major, &QRadioButton::clicked, this, &CompatDB::EnableNext); + connect(ui->radioButton_Graphical_Minor, &QRadioButton::clicked, this, &CompatDB::EnableNext); + connect(ui->radioButton_Graphical_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); + connect(ui->radioButton_Audio_Major, &QRadioButton::clicked, this, &CompatDB::EnableNext); + connect(ui->radioButton_Audio_Minor, &QRadioButton::clicked, this, &CompatDB::EnableNext); + connect(ui->radioButton_Audio_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); + connect(button(NextButton), &QPushButton::clicked, this, &CompatDB::Submit); connect(&testcase_watcher, &QFutureWatcher<bool>::finished, this, &CompatDB::OnTestcaseSubmitted); @@ -30,29 +40,82 @@ CompatDB::~CompatDB() = default; enum class CompatDBPage { Intro = 0, - Selection = 1, - Final = 2, + GameBoot = 1, + GamePlay = 2, + Freeze = 3, + Completion = 4, + Graphical = 5, + Audio = 6, + Final = 7, }; void CompatDB::Submit() { - QButtonGroup* compatibility = new QButtonGroup(this); - compatibility->addButton(ui->radioButton_Perfect, 0); - compatibility->addButton(ui->radioButton_Great, 1); - compatibility->addButton(ui->radioButton_Okay, 2); - compatibility->addButton(ui->radioButton_Bad, 3); - compatibility->addButton(ui->radioButton_IntroMenu, 4); - compatibility->addButton(ui->radioButton_WontBoot, 5); + QButtonGroup* compatibility_GameBoot = new QButtonGroup(this); + compatibility_GameBoot->addButton(ui->radioButton_GameBoot_Yes, 0); + compatibility_GameBoot->addButton(ui->radioButton_GameBoot_No, 1); + + QButtonGroup* compatibility_Gameplay = new QButtonGroup(this); + compatibility_Gameplay->addButton(ui->radioButton_Gameplay_Yes, 0); + compatibility_Gameplay->addButton(ui->radioButton_Gameplay_No, 1); + + QButtonGroup* compatibility_NoFreeze = new QButtonGroup(this); + compatibility_NoFreeze->addButton(ui->radioButton_NoFreeze_Yes, 0); + compatibility_NoFreeze->addButton(ui->radioButton_NoFreeze_No, 1); + + QButtonGroup* compatibility_Complete = new QButtonGroup(this); + compatibility_Complete->addButton(ui->radioButton_Complete_Yes, 0); + compatibility_Complete->addButton(ui->radioButton_Complete_No, 1); + + QButtonGroup* compatibility_Graphical = new QButtonGroup(this); + compatibility_Graphical->addButton(ui->radioButton_Graphical_Major, 0); + compatibility_Graphical->addButton(ui->radioButton_Graphical_Minor, 1); + compatibility_Graphical->addButton(ui->radioButton_Graphical_No, 2); + + QButtonGroup* compatibility_Audio = new QButtonGroup(this); + compatibility_Audio->addButton(ui->radioButton_Audio_Major, 0); + compatibility_Graphical->addButton(ui->radioButton_Audio_Minor, 1); + compatibility_Audio->addButton(ui->radioButton_Audio_No, 2); + + const int compatiblity = static_cast<int>(CalculateCompatibility()); + switch ((static_cast<CompatDBPage>(currentId()))) { - case CompatDBPage::Selection: - if (compatibility->checkedId() == -1) { + case CompatDBPage::Intro: + break; + case CompatDBPage::GameBoot: + if (compatibility_GameBoot->checkedId() == -1) { + button(NextButton)->setEnabled(false); + } + break; + case CompatDBPage::GamePlay: + if (compatibility_Gameplay->checkedId() == -1) { + button(NextButton)->setEnabled(false); + } + break; + case CompatDBPage::Freeze: + if (compatibility_NoFreeze->checkedId() == -1) { + button(NextButton)->setEnabled(false); + } + break; + case CompatDBPage::Completion: + if (compatibility_Complete->checkedId() == -1) { + button(NextButton)->setEnabled(false); + } + break; + case CompatDBPage::Graphical: + if (compatibility_Graphical->checkedId() == -1) { + button(NextButton)->setEnabled(false); + } + break; + case CompatDBPage::Audio: + if (compatibility_Audio->checkedId() == -1) { button(NextButton)->setEnabled(false); } break; case CompatDBPage::Final: back(); - LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId()); + LOG_INFO(Frontend, "Compatibility Rating: {}", compatiblity); telemetry_session.AddField(Common::Telemetry::FieldType::UserFeedback, "Compatibility", - compatibility->checkedId()); + compatiblity); button(NextButton)->setEnabled(false); button(NextButton)->setText(tr("Submitting")); @@ -66,6 +129,66 @@ void CompatDB::Submit() { } } +int CompatDB::nextId() const { + switch ((static_cast<CompatDBPage>(currentId()))) { + case CompatDBPage::Intro: + return static_cast<int>(CompatDBPage::GameBoot); + case CompatDBPage::GameBoot: + if (ui->radioButton_GameBoot_No->isChecked()) { + return static_cast<int>(CompatDBPage::Final); + } + return static_cast<int>(CompatDBPage::GamePlay); + case CompatDBPage::GamePlay: + if (ui->radioButton_Gameplay_No->isChecked()) { + return static_cast<int>(CompatDBPage::Final); + } + return static_cast<int>(CompatDBPage::Freeze); + case CompatDBPage::Freeze: + if (ui->radioButton_NoFreeze_No->isChecked()) { + return static_cast<int>(CompatDBPage::Final); + } + return static_cast<int>(CompatDBPage::Completion); + case CompatDBPage::Completion: + if (ui->radioButton_Complete_No->isChecked()) { + return static_cast<int>(CompatDBPage::Final); + } + return static_cast<int>(CompatDBPage::Graphical); + case CompatDBPage::Graphical: + return static_cast<int>(CompatDBPage::Audio); + case CompatDBPage::Audio: + return static_cast<int>(CompatDBPage::Final); + case CompatDBPage::Final: + return -1; + default: + LOG_ERROR(Frontend, "Unexpected page: {}", currentId()); + return static_cast<int>(CompatDBPage::Intro); + } +} + +CompatibilityStatus CompatDB::CalculateCompatibility() const { + if (ui->radioButton_GameBoot_No->isChecked()) { + return CompatibilityStatus::WontBoot; + } + + if (ui->radioButton_Gameplay_No->isChecked()) { + return CompatibilityStatus::IntroMenu; + } + + if (ui->radioButton_NoFreeze_No->isChecked() || ui->radioButton_Complete_No->isChecked()) { + return CompatibilityStatus::Ingame; + } + + if (ui->radioButton_Graphical_Major->isChecked() || ui->radioButton_Audio_Major->isChecked()) { + return CompatibilityStatus::Ingame; + } + + if (ui->radioButton_Graphical_Minor->isChecked() || ui->radioButton_Audio_Minor->isChecked()) { + return CompatibilityStatus::Playable; + } + + return CompatibilityStatus::Perfect; +} + void CompatDB::OnTestcaseSubmitted() { if (!testcase_watcher.result()) { 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 { class CompatDB; } +enum class CompatibilityStatus { + Perfect = 0, + Playable = 1, + // Unused: Okay = 2, + Ingame = 3, + IntroMenu = 4, + WontBoot = 5, +}; + class CompatDB : public QWizard { Q_OBJECT public: explicit CompatDB(Core::TelemetrySession& telemetry_session_, QWidget* parent = nullptr); ~CompatDB(); + int nextId() const override; private: QFutureWatcher<bool> testcase_watcher; @@ -25,6 +35,7 @@ private: std::unique_ptr<Ui::CompatDB> ui; void Submit(); + CompatibilityStatus CalculateCompatibility() const; void OnTestcaseSubmitted(); void EnableNext(); 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 @@ </item> </layout> </widget> - <widget class="QWizardPage" name="wizard_Report"> + <widget class="QWizardPage" name="wizard_GameBoot"> <property name="title"> <string>Report Game Compatibility</string> </property> <attribute name="pageId"> <string notr="true">1</string> </attribute> - <layout class="QFormLayout" name="formLayout"> + <layout class="QFormLayout" name="formLayout1"> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="lbl_Independent1"> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string><html><head/><body><p>Does the game boot?</p></body></html></string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="0" colspan="2"> + <spacer name="verticalSpacer1"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>0</height> + </size> + </property> + </spacer> + </item> <item row="2" column="0"> - <widget class="QRadioButton" name="radioButton_Perfect"> + <widget class="QRadioButton" name="radioButton_GameBoot_Yes"> <property name="text"> - <string>Perfect</string> + <string>Yes The game starts to output video or audio</string> </property> </widget> </item> - <item row="2" column="1"> - <widget class="QLabel" name="lbl_Perfect"> + <item row="4" column="0"> + <widget class="QRadioButton" name="radioButton_GameBoot_No"> <property name="text"> - <string><html><head/><body><p>Game functions flawlessly with no audio or graphical glitches.</p></body></html></string> + <string>No The game doesn't get past the "Launching..." screen</string> </property> - <property name="wordWrap"> - <bool>true</bool> + </widget> + </item> + </layout> + </widget> + <widget class="QWizardPage" name="wizard_GamePlay"> + <property name="title"> + <string>Report Game Compatibility</string> + </property> + <attribute name="pageId"> + <string notr="true">2</string> + </attribute> + <layout class="QFormLayout" name="formLayout2"> + <item row="2" column="0"> + <widget class="QRadioButton" name="radioButton_Gameplay_Yes"> + <property name="text"> + <string>Yes The game gets past the intro/menu and into gameplay</string> </property> </widget> </item> <item row="4" column="0"> - <widget class="QRadioButton" name="radioButton_Great"> + <widget class="QRadioButton" name="radioButton_Gameplay_No"> <property name="text"> - <string>Great</string> + <string>No The game crashes or freezes while loading or using the menu</string> </property> </widget> </item> - <item row="4" column="1"> - <widget class="QLabel" name="lbl_Great"> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="lbl_Independent2"> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> <property name="text"> - <string><html><head/><body><p>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.</p></body></html></string> + <string><html><head/><body><p>Does the game reach gameplay?</p></body></html></string> </property> <property name="wordWrap"> <bool>true</bool> </property> </widget> </item> - <item row="5" column="0"> - <widget class="QRadioButton" name="radioButton_Okay"> + <item row="1" column="0" colspan="2"> + <spacer name="verticalSpacer2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="QWizardPage" name="wizard_NoFreeze"> + <property name="title"> + <string>Report Game Compatibility</string> + </property> + <attribute name="pageId"> + <string notr="true">3</string> + </attribute> + <layout class="QFormLayout" name="formLayout3"> + <item row="2" column="0"> + <widget class="QRadioButton" name="radioButton_NoFreeze_Yes"> <property name="text"> - <string>Okay</string> + <string>Yes The game works without crashes</string> </property> </widget> </item> - <item row="5" column="1"> - <widget class="QLabel" name="lbl_Okay"> + <item row="4" column="0"> + <widget class="QRadioButton" name="radioButton_NoFreeze_No"> + <property name="text"> + <string>No The game crashes or freezes during gameplay</string> + </property> + </widget> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="lbl_Independent3"> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> <property name="text"> - <string><html><head/><body><p>Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.</p></body></html></string> + <string><html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html></string> </property> <property name="wordWrap"> <bool>true</bool> </property> </widget> </item> - <item row="6" column="0"> - <widget class="QRadioButton" name="radioButton_Bad"> + <item row="1" column="0" colspan="2"> + <spacer name="verticalSpacer3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="QWizardPage" name="wizard_Complete"> + <property name="title"> + <string>Report Game Compatibility</string> + </property> + <attribute name="pageId"> + <string notr="true">4</string> + </attribute> + <layout class="QFormLayout" name="formLayout4"> + <item row="2" column="0"> + <widget class="QRadioButton" name="radioButton_Complete_Yes"> <property name="text"> - <string>Bad</string> + <string>Yes The game can be finished without any workarounds</string> </property> </widget> </item> - <item row="6" column="1"> - <widget class="QLabel" name="lbl_Bad"> + <item row="4" column="0"> + <widget class="QRadioButton" name="radioButton_Complete_No"> + <property name="text"> + <string>No The game can't progress past a certain area</string> + </property> + </widget> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="lbl_Independent4"> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> <property name="text"> - <string><html><head/><body><p>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.</p></body></html></string> + <string><html><head/><body><p>Is the game completely playable from start to finish?</p></body></html></string> </property> <property name="wordWrap"> <bool>true</bool> </property> </widget> </item> - <item row="7" column="0"> - <widget class="QRadioButton" name="radioButton_IntroMenu"> + <item row="1" column="0" colspan="2"> + <spacer name="verticalSpacer4"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="QWizardPage" name="wizard_Graphical"> + <property name="title"> + <string>Report Game Compatibility</string> + </property> + <attribute name="pageId"> + <string notr="true">5</string> + </attribute> + <layout class="QFormLayout" name="formLayout5"> + <item row="2" column="0"> + <widget class="QRadioButton" name="radioButton_Graphical_Major"> + <property name="text"> + <string>Major The game has major graphical errors</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QRadioButton" name="radioButton_Graphical_Minor"> <property name="text"> - <string>Intro/Menu</string> + <string>Minor The game has minor graphical errors</string> </property> </widget> </item> - <item row="7" column="1"> - <widget class="QLabel" name="lbl_IntroMenu"> + <item row="6" column="0"> + <widget class="QRadioButton" name="radioButton_Graphical_No"> + <property name="text"> + <string>None Everything is rendered as it looks on the Nintendo Switch</string> + </property> + </widget> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="lbl_Independent5"> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> <property name="text"> - <string><html><head/><body><p>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.</p></body></html></string> + <string><html><head/><body><p>Does the game have any graphical glitches?</p></body></html></string> </property> <property name="wordWrap"> <bool>true</bool> </property> </widget> </item> - <item row="8" column="0"> - <widget class="QRadioButton" name="radioButton_WontBoot"> - <property name="text"> - <string>Won't Boot</string> + <item row="1" column="0" colspan="2"> + <spacer name="verticalSpacer5"> + <property name="orientation"> + <enum>Qt::Vertical</enum> </property> - <property name="checkable"> - <bool>true</bool> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>0</height> + </size> </property> - <property name="checked"> - <bool>false</bool> + </spacer> + </item> + </layout> + </widget> + <widget class="QWizardPage" name="wizard_Audio"> + <property name="title"> + <string>Report Game Compatibility</string> + </property> + <attribute name="pageId"> + <string notr="true">6</string> + </attribute> + <layout class="QFormLayout" name="formLayout6"> + <item row="2" column="0"> + <widget class="QRadioButton" name="radioButton_Audio_Major"> + <property name="text"> + <string>Major The game has major audio errors</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QRadioButton" name="radioButton_Audio_Minor"> + <property name="text"> + <string>Minor The game has minor audio errors</string> </property> </widget> </item> - <item row="8" column="1"> - <widget class="QLabel" name="lbl_WontBoot"> + <item row="6" column="0"> + <widget class="QRadioButton" name="radioButton_Audio_No"> <property name="text"> - <string><html><head/><body><p>The game crashes when attempting to startup.</p></body></html></string> + <string>None Audio is played perfectly</string> </property> </widget> </item> <item row="0" column="0" colspan="2"> - <widget class="QLabel" name="lbl_Independent"> + <widget class="QLabel" name="lbl_Independent6"> <property name="font"> <font> <pointsize>10</pointsize> </font> </property> <property name="text"> - <string><html><head/><body><p>Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?</p></body></html></string> + <string><html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html></string> </property> <property name="wordWrap"> <bool>true</bool> @@ -187,7 +370,7 @@ </widget> </item> <item row="1" column="0" colspan="2"> - <spacer name="verticalSpacer"> + <spacer name="verticalSpacer6"> <property name="orientation"> <enum>Qt::Vertical</enum> </property> @@ -206,7 +389,7 @@ <string>Thank you for your submission!</string> </property> <attribute name="pageId"> - <string notr="true">2</string> + <string notr="true">7</string> </attribute> </widget> </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: const char* tooltip; }; // clang-format off + const auto ingame_status = + CompatStatus{QStringLiteral("#f2d624"), QT_TR_NOOP("Ingame"), QT_TR_NOOP("Game starts, but crashes or major glitches prevent it from being completed.")}; static const std::map<QString, CompatStatus> status_data = { - {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.")}}, - {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.")}}, - {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.")}}, - {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.")}}, - {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.")}}, + {QStringLiteral("0"), {QStringLiteral("#5c93ed"), QT_TR_NOOP("Perfect"), QT_TR_NOOP("Game can be played without issues.")}}, + {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.")}}, + {QStringLiteral("2"), ingame_status}, + {QStringLiteral("3"), ingame_status}, // Fallback for the removed "Okay" category + {QStringLiteral("4"), {QStringLiteral("#FF0000"), QT_TR_NOOP("Intro/Menu"), QT_TR_NOOP("Game loads, but is unable to progress past the Start Screen.")}}, {QStringLiteral("5"), {QStringLiteral("#828282"), QT_TR_NOOP("Won't Boot"), QT_TR_NOOP("The game crashes when attempting to startup.")}}, {QStringLiteral("99"), {QStringLiteral("#000000"), QT_TR_NOOP("Not Tested"), QT_TR_NOOP("The game has not yet been tested.")}}, }; 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 const auto override_build = fmt::format(fmt::runtime(std::string(Common::g_title_bar_format_idle)), build_id); const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build; + const auto processor_count = std::thread::hardware_concurrency(); LOG_INFO(Frontend, "yuzu Version: {}", yuzu_build_version); LogRuntimes(); @@ -361,6 +362,11 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan } LOG_INFO(Frontend, "Host CPU: {}", cpu_string); #endif + + if (std::optional<int> processor_core = Common::GetProcessorCount()) { + LOG_INFO(Frontend, "Host CPU Cores: {}", *processor_core); + } + LOG_INFO(Frontend, "Host CPU Threads: {}", processor_count); LOG_INFO(Frontend, "Host OS: {}", PrettyProductName().toStdString()); LOG_INFO(Frontend, "Host RAM: {:.2f} GiB", Common::GetMemInfo().TotalPhysicalMemory / f64{1_GiB}); @@ -2815,6 +2821,20 @@ void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_tex } void GMainWindow::OnMenuReportCompatibility() { + const auto& caps = Common::GetCPUCaps(); + const bool has_fma = caps.fma || caps.fma4; + const auto processor_count = std::thread::hardware_concurrency(); + const bool has_4threads = processor_count == 0 || processor_count >= 4; + const bool has_8gb_ram = Common::GetMemInfo().TotalPhysicalMemory >= 8_GiB; + const bool has_broken_vulkan = UISettings::values.has_broken_vulkan; + + if (!has_fma || !has_4threads || !has_8gb_ram || has_broken_vulkan) { + QMessageBox::critical(this, tr("Hardware requirements not met"), + tr("Your system does not meet the recommended hardware requirements. " + "Compatibility reporting has been disabled.")); + return; + } + if (!Settings::values.yuzu_token.GetValue().empty() && !Settings::values.yuzu_username.GetValue().empty()) { CompatDB compatdb{system->TelemetrySession(), this}; |