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}; | 
