diff options
25 files changed, 489 insertions, 314 deletions
diff --git a/src/common/scope_exit.h b/src/common/scope_exit.h index baf1f1c9e..1176a72b1 100644 --- a/src/common/scope_exit.h +++ b/src/common/scope_exit.h @@ -20,7 +20,7 @@ struct ScopeExitHelper {  template <typename Func>  ScopeExitHelper<Func> ScopeExit(Func&& func) { -    return ScopeExitHelper<Func>(std::move(func)); +    return ScopeExitHelper<Func>(std::forward<Func>(func));  }  } // namespace detail diff --git a/src/common/swap.h b/src/common/swap.h index b3eab1324..71932c2bb 100644 --- a/src/common/swap.h +++ b/src/common/swap.h @@ -21,11 +21,6 @@  #if defined(_MSC_VER)  #include <cstdlib> -#elif defined(__linux__) -#include <byteswap.h> -#elif defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) ||                     \ -    defined(__NetBSD__) || defined(__OpenBSD__) -#include <sys/endian.h>  #endif  #include <cstring>  #include "common/common_types.h" @@ -62,86 +57,49 @@  namespace Common {  #ifdef _MSC_VER -inline u16 swap16(u16 _data) { -    return _byteswap_ushort(_data); +[[nodiscard]] inline u16 swap16(u16 data) noexcept { +    return _byteswap_ushort(data);  } -inline u32 swap32(u32 _data) { -    return _byteswap_ulong(_data); +[[nodiscard]] inline u32 swap32(u32 data) noexcept { +    return _byteswap_ulong(data);  } -inline u64 swap64(u64 _data) { -    return _byteswap_uint64(_data); +[[nodiscard]] inline u64 swap64(u64 data) noexcept { +    return _byteswap_uint64(data);  } -#elif defined(ARCHITECTURE_ARM) && (__ARM_ARCH >= 6) -inline u16 swap16(u16 _data) { -    u32 data = _data; -    __asm__("rev16 %0, %1\n" : "=l"(data) : "l"(data)); -    return (u16)data; -} -inline u32 swap32(u32 _data) { -    __asm__("rev %0, %1\n" : "=l"(_data) : "l"(_data)); -    return _data; -} -inline u64 swap64(u64 _data) { -    return ((u64)swap32(_data) << 32) | swap32(_data >> 32); -} -#elif __linux__ -inline u16 swap16(u16 _data) { -    return bswap_16(_data); -} -inline u32 swap32(u32 _data) { -    return bswap_32(_data); -} -inline u64 swap64(u64 _data) { -    return bswap_64(_data); -} -#elif __APPLE__ -inline __attribute__((always_inline)) u16 swap16(u16 _data) { -    return (_data >> 8) | (_data << 8); -} -inline __attribute__((always_inline)) u32 swap32(u32 _data) { -    return __builtin_bswap32(_data); -} -inline __attribute__((always_inline)) u64 swap64(u64 _data) { -    return __builtin_bswap64(_data); -} -#elif defined(__Bitrig__) || defined(__OpenBSD__) +#elif defined(__clang__) || defined(__GNUC__) +#if defined(__Bitrig__) || defined(__OpenBSD__)  // redefine swap16, swap32, swap64 as inline functions  #undef swap16  #undef swap32  #undef swap64 -inline u16 swap16(u16 _data) { -    return __swap16(_data); -} -inline u32 swap32(u32 _data) { -    return __swap32(_data); -} -inline u64 swap64(u64 _data) { -    return __swap64(_data); -} -#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) -inline u16 swap16(u16 _data) { -    return bswap16(_data); +#endif +[[nodiscard]] inline u16 swap16(u16 data) noexcept { +    return __builtin_bswap16(data);  } -inline u32 swap32(u32 _data) { -    return bswap32(_data); +[[nodiscard]] inline u32 swap32(u32 data) noexcept { +    return __builtin_bswap32(data);  } -inline u64 swap64(u64 _data) { -    return bswap64(_data); +[[nodiscard]] inline u64 swap64(u64 data) noexcept { +    return __builtin_bswap64(data);  }  #else -// Slow generic implementation. -inline u16 swap16(u16 data) { +// Generic implementation. +[[nodiscard]] inline u16 swap16(u16 data) noexcept {      return (data >> 8) | (data << 8);  } -inline u32 swap32(u32 data) { -    return (swap16(data) << 16) | swap16(data >> 16); +[[nodiscard]] inline u32 swap32(u32 data) noexcept { +    return ((data & 0xFF000000U) >> 24) | ((data & 0x00FF0000U) >> 8) | +           ((data & 0x0000FF00U) << 8) | ((data & 0x000000FFU) << 24);  } -inline u64 swap64(u64 data) { -    return ((u64)swap32(data) << 32) | swap32(data >> 32); +[[nodiscard]] inline u64 swap64(u64 data) noexcept { +    return ((data & 0xFF00000000000000ULL) >> 56) | ((data & 0x00FF000000000000ULL) >> 40) | +           ((data & 0x0000FF0000000000ULL) >> 24) | ((data & 0x000000FF00000000ULL) >> 8) | +           ((data & 0x00000000FF000000ULL) << 8) | ((data & 0x0000000000FF0000ULL) << 24) | +           ((data & 0x000000000000FF00ULL) << 40) | ((data & 0x00000000000000FFULL) << 56);  }  #endif -inline float swapf(float f) { +[[nodiscard]] inline float swapf(float f) noexcept {      static_assert(sizeof(u32) == sizeof(float), "float must be the same size as uint32_t.");      u32 value; @@ -153,7 +111,7 @@ inline float swapf(float f) {      return f;  } -inline double swapd(double f) { +[[nodiscard]] inline double swapd(double f) noexcept {      static_assert(sizeof(u64) == sizeof(double), "double must be the same size as uint64_t.");      u64 value; diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index d0bcb4660..70a522556 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -13,6 +13,23 @@  namespace Core::Frontend {  /** + * Represents a graphics context that can be used for background computation or drawing. If the + * graphics backend doesn't require the context, then the implementation of these methods can be + * stubs + */ +class GraphicsContext { +public: +    /// Makes the graphics context current for the caller thread +    virtual void MakeCurrent() = 0; + +    /// Releases (dunno if this is the "right" word) the context from the caller thread +    virtual void DoneCurrent() = 0; + +    /// Swap buffers to display the next frame +    virtual void SwapBuffers() = 0; +}; + +/**   * Abstraction class used to provide an interface between emulation code and the frontend   * (e.g. SDL, QGLWidget, GLFW, etc...).   * @@ -30,7 +47,7 @@ namespace Core::Frontend {   * - DO NOT TREAT THIS CLASS AS A GUI TOOLKIT ABSTRACTION LAYER. That's not what it is. Please   *   re-read the upper points again and think about it if you don't see this.   */ -class EmuWindow { +class EmuWindow : public GraphicsContext {  public:      /// Data structure to store emuwindow configuration      struct WindowConfig { @@ -40,17 +57,21 @@ public:          std::pair<unsigned, unsigned> min_client_area_size;      }; -    /// Swap buffers to display the next frame -    virtual void SwapBuffers() = 0; -      /// Polls window events      virtual void PollEvents() = 0; -    /// Makes the graphics context current for the caller thread -    virtual void MakeCurrent() = 0; - -    /// Releases (dunno if this is the "right" word) the GLFW context from the caller thread -    virtual void DoneCurrent() = 0; +    /** +     * Returns a GraphicsContext that the frontend provides that is shared with the emu window. This +     * context can be used from other threads for background graphics computation. If the frontend +     * is using a graphics backend that doesn't need anything specific to run on a different thread, +     * then it can use a stubbed implemenation for GraphicsContext. +     * +     * If the return value is null, then the core should assume that the frontend cannot provide a +     * Shared Context +     */ +    virtual std::unique_ptr<GraphicsContext> CreateSharedContext() const { +        return nullptr; +    }      /**       * Signal that a touch pressed event has occurred (e.g. mouse click pressed) diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 1b891f632..ca52267b2 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -220,11 +220,6 @@ void Thread::SetPriority(u32 priority) {      UpdatePriority();  } -void Thread::BoostPriority(u32 priority) { -    scheduler->SetThreadPriority(this, priority); -    current_priority = priority; -} -  void Thread::SetWaitSynchronizationResult(ResultCode result) {      context.cpu_registers[0] = result.raw;  } diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 83c83e45a..32026d7f0 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -136,12 +136,6 @@ public:       */      void SetPriority(u32 priority); -    /** -     * Temporarily boosts the thread's priority until the next time it is scheduled -     * @param priority The new priority -     */ -    void BoostPriority(u32 priority); -      /// Adds a thread to the list of threads that are waiting for a lock held by this thread.      void AddMutexWaiter(SharedPtr<Thread> thread); diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index 657baddb8..0249b6992 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -115,11 +115,12 @@ private:      void Read(Kernel::HLERequestContext& ctx) {          IPC::RequestParser rp{ctx}; -        const u64 unk = rp.Pop<u64>(); +        const u64 option = rp.Pop<u64>();          const s64 offset = rp.Pop<s64>();          const s64 length = rp.Pop<s64>(); -        LOG_DEBUG(Service_FS, "called, offset=0x{:X}, length={}", offset, length); +        LOG_DEBUG(Service_FS, "called, option={}, offset=0x{:X}, length={}", option, offset, +                  length);          // Error checking          if (length < 0) { @@ -148,11 +149,12 @@ private:      void Write(Kernel::HLERequestContext& ctx) {          IPC::RequestParser rp{ctx}; -        const u64 unk = rp.Pop<u64>(); +        const u64 option = rp.Pop<u64>();          const s64 offset = rp.Pop<s64>();          const s64 length = rp.Pop<s64>(); -        LOG_DEBUG(Service_FS, "called, offset=0x{:X}, length={}", offset, length); +        LOG_DEBUG(Service_FS, "called, option={}, offset=0x{:X}, length={}", option, offset, +                  length);          // Error checking          if (length < 0) { @@ -250,10 +252,7 @@ private:      u64 next_entry_index = 0;      void Read(Kernel::HLERequestContext& ctx) { -        IPC::RequestParser rp{ctx}; -        const u64 unk = rp.Pop<u64>(); - -        LOG_DEBUG(Service_FS, "called, unk=0x{:X}", unk); +        LOG_DEBUG(Service_FS, "called.");          // Calculate how many entries we can fit in the output buffer          const u64 count_entries = ctx.GetWriteBufferSize() / sizeof(FileSys::Entry); diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index d65693fc7..609102f2c 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp @@ -93,12 +93,18 @@ public:      }      void LoadNrr(Kernel::HLERequestContext& ctx) { +        struct Parameters { +            u64_le process_id; +            u64_le nrr_address; +            u64_le nrr_size; +        }; +          IPC::RequestParser rp{ctx}; -        rp.Skip(2, false); -        const VAddr nrr_addr{rp.Pop<VAddr>()}; -        const u64 nrr_size{rp.Pop<u64>()}; -        LOG_DEBUG(Service_LDR, "called with nrr_addr={:016X}, nrr_size={:016X}", nrr_addr, -                  nrr_size); +        const auto [process_id, nrr_address, nrr_size] = rp.PopRaw<Parameters>(); + +        LOG_DEBUG(Service_LDR, +                  "called with process_id={:016X}, nrr_address={:016X}, nrr_size={:016X}", +                  process_id, nrr_address, nrr_size);          if (!initialized) {              LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!"); @@ -116,24 +122,26 @@ public:          }          // NRR Address does not fall on 0x1000 byte boundary -        if (!Common::Is4KBAligned(nrr_addr)) { -            LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!", nrr_addr); +        if (!Common::Is4KBAligned(nrr_address)) { +            LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!", +                      nrr_address);              IPC::ResponseBuilder rb{ctx, 2};              rb.Push(ERROR_INVALID_ALIGNMENT);              return;          }          // NRR Size is zero or causes overflow -        if (nrr_addr + nrr_size <= nrr_addr || nrr_size == 0 || !Common::Is4KBAligned(nrr_size)) { +        if (nrr_address + nrr_size <= nrr_address || nrr_size == 0 || +            !Common::Is4KBAligned(nrr_size)) {              LOG_ERROR(Service_LDR, "NRR Size is invalid! (nrr_address={:016X}, nrr_size={:016X})", -                      nrr_addr, nrr_size); +                      nrr_address, nrr_size);              IPC::ResponseBuilder rb{ctx, 2};              rb.Push(ERROR_INVALID_SIZE);              return;          }          // Read NRR data from memory          std::vector<u8> nrr_data(nrr_size); -        Memory::ReadBlock(nrr_addr, nrr_data.data(), nrr_size); +        Memory::ReadBlock(nrr_address, nrr_data.data(), nrr_size);          NRRHeader header;          std::memcpy(&header, nrr_data.data(), sizeof(NRRHeader)); @@ -174,7 +182,7 @@ public:              hashes.emplace_back(hash);          } -        nrr.insert_or_assign(nrr_addr, std::move(hashes)); +        nrr.insert_or_assign(nrr_address, std::move(hashes));          IPC::ResponseBuilder rb{ctx, 2};          rb.Push(RESULT_SUCCESS); @@ -188,23 +196,30 @@ public:              return;          } +        struct Parameters { +            u64_le process_id; +            u64_le nrr_address; +        }; +          IPC::RequestParser rp{ctx}; -        rp.Skip(2, false); -        const auto nrr_addr{rp.Pop<VAddr>()}; -        LOG_DEBUG(Service_LDR, "called with nrr_addr={:016X}", nrr_addr); +        const auto [process_id, nrr_address] = rp.PopRaw<Parameters>(); + +        LOG_DEBUG(Service_LDR, "called with process_id={:016X}, nrr_addr={:016X}", process_id, +                  nrr_address); -        if (!Common::Is4KBAligned(nrr_addr)) { -            LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!", nrr_addr); +        if (!Common::Is4KBAligned(nrr_address)) { +            LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!", +                      nrr_address);              IPC::ResponseBuilder rb{ctx, 2};              rb.Push(ERROR_INVALID_ALIGNMENT);              return;          } -        const auto iter = nrr.find(nrr_addr); +        const auto iter = nrr.find(nrr_address);          if (iter == nrr.end()) {              LOG_ERROR(Service_LDR,                        "Attempting to unload NRR which has not been loaded! (addr={:016X})", -                      nrr_addr); +                      nrr_address);              IPC::ResponseBuilder rb{ctx, 2};              rb.Push(ERROR_INVALID_NRR_ADDRESS);              return; @@ -216,16 +231,22 @@ public:      }      void LoadNro(Kernel::HLERequestContext& ctx) { +        struct Parameters { +            u64_le process_id; +            u64_le image_address; +            u64_le image_size; +            u64_le bss_address; +            u64_le bss_size; +        }; +          IPC::RequestParser rp{ctx}; -        rp.Skip(2, false); -        const VAddr nro_addr{rp.Pop<VAddr>()}; -        const u64 nro_size{rp.Pop<u64>()}; -        const VAddr bss_addr{rp.Pop<VAddr>()}; -        const u64 bss_size{rp.Pop<u64>()}; -        LOG_DEBUG( -            Service_LDR, -            "called with nro_addr={:016X}, nro_size={:016X}, bss_addr={:016X}, bss_size={:016X}", -            nro_addr, nro_size, bss_addr, bss_size); +        const auto [process_id, nro_address, nro_size, bss_address, bss_size] = +            rp.PopRaw<Parameters>(); + +        LOG_DEBUG(Service_LDR, +                  "called with pid={:016X}, nro_addr={:016X}, nro_size={:016X}, bss_addr={:016X}, " +                  "bss_size={:016X}", +                  process_id, nro_address, nro_size, bss_address, bss_size);          if (!initialized) {              LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!"); @@ -243,8 +264,9 @@ public:          }          // NRO Address does not fall on 0x1000 byte boundary -        if (!Common::Is4KBAligned(nro_addr)) { -            LOG_ERROR(Service_LDR, "NRO Address has invalid alignment (actual {:016X})!", nro_addr); +        if (!Common::Is4KBAligned(nro_address)) { +            LOG_ERROR(Service_LDR, "NRO Address has invalid alignment (actual {:016X})!", +                      nro_address);              IPC::ResponseBuilder rb{ctx, 2};              rb.Push(ERROR_INVALID_ALIGNMENT);              return; @@ -252,15 +274,15 @@ public:          // NRO Size or BSS Size is zero or causes overflow          const auto nro_size_valid = -            nro_size != 0 && nro_addr + nro_size > nro_addr && Common::Is4KBAligned(nro_size); -        const auto bss_size_valid = -            nro_size + bss_size >= nro_size && (bss_size == 0 || bss_addr + bss_size > bss_addr); +            nro_size != 0 && nro_address + nro_size > nro_address && Common::Is4KBAligned(nro_size); +        const auto bss_size_valid = nro_size + bss_size >= nro_size && +                                    (bss_size == 0 || bss_address + bss_size > bss_address);          if (!nro_size_valid || !bss_size_valid) {              LOG_ERROR(Service_LDR,                        "NRO Size or BSS Size is invalid! (nro_address={:016X}, nro_size={:016X}, "                        "bss_address={:016X}, bss_size={:016X})", -                      nro_addr, nro_size, bss_addr, bss_size); +                      nro_address, nro_size, bss_address, bss_size);              IPC::ResponseBuilder rb{ctx, 2};              rb.Push(ERROR_INVALID_SIZE);              return; @@ -268,7 +290,7 @@ public:          // Read NRO data from memory          std::vector<u8> nro_data(nro_size); -        Memory::ReadBlock(nro_addr, nro_data.data(), nro_size); +        Memory::ReadBlock(nro_address, nro_data.data(), nro_size);          SHA256Hash hash{};          mbedtls_sha256(nro_data.data(), nro_data.size(), hash.data(), 0); @@ -318,17 +340,18 @@ public:              return;          } -        ASSERT(vm_manager -                   .MirrorMemory(*map_address, nro_addr, nro_size, Kernel::MemoryState::ModuleCode) -                   .IsSuccess()); -        ASSERT(vm_manager.UnmapRange(nro_addr, nro_size).IsSuccess()); +        ASSERT( +            vm_manager +                .MirrorMemory(*map_address, nro_address, nro_size, Kernel::MemoryState::ModuleCode) +                .IsSuccess()); +        ASSERT(vm_manager.UnmapRange(nro_address, nro_size).IsSuccess());          if (bss_size > 0) {              ASSERT(vm_manager -                       .MirrorMemory(*map_address + nro_size, bss_addr, bss_size, +                       .MirrorMemory(*map_address + nro_size, bss_address, bss_size,                                       Kernel::MemoryState::ModuleCode)                         .IsSuccess()); -            ASSERT(vm_manager.UnmapRange(bss_addr, bss_size).IsSuccess()); +            ASSERT(vm_manager.UnmapRange(bss_address, bss_size).IsSuccess());          }          vm_manager.ReprotectRange(*map_address, header.text_size, @@ -348,13 +371,6 @@ public:      }      void UnloadNro(Kernel::HLERequestContext& ctx) { -        IPC::RequestParser rp{ctx}; -        rp.Skip(2, false); -        const VAddr mapped_addr{rp.PopRaw<VAddr>()}; -        const VAddr heap_addr{rp.PopRaw<VAddr>()}; -        LOG_DEBUG(Service_LDR, "called with mapped_addr={:016X}, heap_addr={:016X}", mapped_addr, -                  heap_addr); -          if (!initialized) {              LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!");              IPC::ResponseBuilder rb{ctx, 2}; @@ -362,22 +378,30 @@ public:              return;          } -        if (!Common::Is4KBAligned(mapped_addr) || !Common::Is4KBAligned(heap_addr)) { -            LOG_ERROR(Service_LDR, -                      "NRO/BSS Address has invalid alignment (actual nro_addr={:016X}, " -                      "bss_addr={:016X})!", -                      mapped_addr, heap_addr); +        struct Parameters { +            u64_le process_id; +            u64_le nro_address; +        }; + +        IPC::RequestParser rp{ctx}; +        const auto [process_id, nro_address] = rp.PopRaw<Parameters>(); +        LOG_DEBUG(Service_LDR, "called with process_id={:016X}, nro_address=0x{:016X}", process_id, +                  nro_address); + +        if (!Common::Is4KBAligned(nro_address)) { +            LOG_ERROR(Service_LDR, "NRO address has invalid alignment (nro_address=0x{:016X})", +                      nro_address);              IPC::ResponseBuilder rb{ctx, 2};              rb.Push(ERROR_INVALID_ALIGNMENT);              return;          } -        const auto iter = nro.find(mapped_addr); +        const auto iter = nro.find(nro_address);          if (iter == nro.end()) {              LOG_ERROR(Service_LDR, -                      "The NRO attempting to unmap was not mapped or has an invalid address " -                      "(actual {:016X})!", -                      mapped_addr); +                      "The NRO attempting to be unmapped was not mapped or has an invalid address " +                      "(nro_address=0x{:016X})!", +                      nro_address);              IPC::ResponseBuilder rb{ctx, 2};              rb.Push(ERROR_INVALID_NRO_ADDRESS);              return; @@ -386,10 +410,7 @@ public:          auto& vm_manager = Core::CurrentProcess()->VMManager();          const auto& nro_size = iter->second.size; -        ASSERT(vm_manager -                   .MirrorMemory(heap_addr, mapped_addr, nro_size, Kernel::MemoryState::ModuleCode) -                   .IsSuccess()); -        ASSERT(vm_manager.UnmapRange(mapped_addr, nro_size).IsSuccess()); +        ASSERT(vm_manager.UnmapRange(nro_address, nro_size).IsSuccess());          Core::System::GetInstance().InvalidateCpuInstructionCaches(); @@ -459,11 +480,10 @@ private:      std::map<VAddr, NROInfo> nro;      std::map<VAddr, std::vector<SHA256Hash>> nrr; -    bool IsValidNROHash(const SHA256Hash& hash) { -        return std::any_of( -            nrr.begin(), nrr.end(), [&hash](const std::pair<VAddr, std::vector<SHA256Hash>>& p) { -                return std::find(p.second.begin(), p.second.end(), hash) != p.second.end(); -            }); +    bool IsValidNROHash(const SHA256Hash& hash) const { +        return std::any_of(nrr.begin(), nrr.end(), [&hash](const auto& p) { +            return std::find(p.second.begin(), p.second.end(), hash) != p.second.end(); +        });      }      static bool IsValidNRO(const NROHeader& header, u64 nro_size, u64 bss_size) { diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index c7f5bbf28..3c5c53e24 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -21,12 +21,13 @@  #include "core/hle/service/vi/display/vi_display.h"  #include "core/hle/service/vi/layer/vi_layer.h"  #include "core/perf_stats.h" +#include "core/settings.h"  #include "video_core/renderer_base.h"  namespace Service::NVFlinger { -constexpr std::size_t SCREEN_REFRESH_RATE = 60; -constexpr s64 frame_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / SCREEN_REFRESH_RATE); +constexpr s64 frame_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60); +constexpr s64 frame_ticks_30fps = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 30);  NVFlinger::NVFlinger(Core::Timing::CoreTiming& core_timing) : core_timing{core_timing} {      displays.emplace_back(0, "Default"); @@ -36,13 +37,15 @@ NVFlinger::NVFlinger(Core::Timing::CoreTiming& core_timing) : core_timing{core_t      displays.emplace_back(4, "Null");      // Schedule the screen composition events -    composition_event = -        core_timing.RegisterEvent("ScreenComposition", [this](u64 userdata, s64 cycles_late) { +    const auto ticks = Settings::values.force_30fps_mode ? frame_ticks_30fps : frame_ticks; + +    composition_event = core_timing.RegisterEvent( +        "ScreenComposition", [this, ticks](u64 userdata, s64 cycles_late) {              Compose(); -            this->core_timing.ScheduleEvent(frame_ticks - cycles_late, composition_event); +            this->core_timing.ScheduleEvent(ticks - cycles_late, composition_event);          }); -    core_timing.ScheduleEvent(frame_ticks, composition_event); +    core_timing.ScheduleEvent(ticks, composition_event);  }  NVFlinger::~NVFlinger() { @@ -62,6 +65,7 @@ std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) {      const auto itr =          std::find_if(displays.begin(), displays.end(),                       [&](const VI::Display& display) { return display.GetName() == name; }); +      if (itr == displays.end()) {          return {};      } diff --git a/src/core/settings.h b/src/core/settings.h index d543eb32f..b84390745 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -393,6 +393,7 @@ struct Values {      bool use_disk_shader_cache;      bool use_accurate_gpu_emulation;      bool use_asynchronous_gpu_emulation; +    bool force_30fps_mode;      float bg_red;      float bg_green; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 55b6d8591..7a68b8738 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -112,11 +112,26 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only,      params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(),                                                         params.srgb_conversion); -    if (params.pixel_format == PixelFormat::R16U && config.tsc.depth_compare_enabled) { +    if (config.tsc.depth_compare_enabled) {          // Some titles create a 'R16U' (normalized 16-bit) texture with depth_compare enabled,          // then attempt to sample from it via a shadow sampler. Convert format to Z16 (which also          // causes GetFormatType to properly return 'Depth' below). -        params.pixel_format = PixelFormat::Z16; +        if (GetFormatType(params.pixel_format) == SurfaceType::ColorTexture) { +            switch (params.pixel_format) { +            case PixelFormat::R16S: +            case PixelFormat::R16U: +            case PixelFormat::R16F: +                params.pixel_format = PixelFormat::Z16; +                break; +            case PixelFormat::R32F: +                params.pixel_format = PixelFormat::Z32F; +                break; +            default: +                LOG_WARNING(HW_GPU, "Color texture format being used with depth compare: {}", +                            static_cast<u32>(params.pixel_format)); +                break; +            } +        }      }      params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value()); @@ -266,10 +281,7 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only,      params.component_type = ComponentTypeFromRenderTarget(config.format);      params.type = GetFormatType(params.pixel_format);      params.width = config.width; -    if (!params.is_tiled) { -        const u32 bpp = params.GetFormatBpp() / 8; -        params.pitch = config.width * bpp; -    } +    params.pitch = config.pitch;      params.height = config.height;      params.unaligned_height = config.height;      params.target = SurfaceTarget::Texture2D; diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp index eaf3e03a0..05ab01dcb 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.cpp +++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp @@ -2,12 +2,44 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include "common/common_types.h" +#include "video_core/engines/maxwell_3d.h"  #include "video_core/renderer_opengl/gl_shader_manager.h"  namespace OpenGL::GLShader {  using Tegra::Engines::Maxwell3D; +ProgramManager::ProgramManager() { +    pipeline.Create(); +} + +ProgramManager::~ProgramManager() = default; + +void ProgramManager::ApplyTo(OpenGLState& state) { +    UpdatePipeline(); +    state.draw.shader_program = 0; +    state.draw.program_pipeline = pipeline.handle; +} + +void ProgramManager::UpdatePipeline() { +    // Avoid updating the pipeline when values have no changed +    if (old_state == current_state) { +        return; +    } + +    // Workaround for AMD bug +    constexpr GLenum all_used_stages{GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | +                                     GL_FRAGMENT_SHADER_BIT}; +    glUseProgramStages(pipeline.handle, all_used_stages, 0); + +    glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, current_state.vertex_shader); +    glUseProgramStages(pipeline.handle, GL_GEOMETRY_SHADER_BIT, current_state.geometry_shader); +    glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, current_state.fragment_shader); + +    old_state = current_state; +} +  void MaxwellUniformData::SetFromRegs(const Maxwell3D& maxwell, std::size_t shader_stage) {      const auto& regs = maxwell.regs;      const auto& state = maxwell.state; @@ -16,7 +48,7 @@ void MaxwellUniformData::SetFromRegs(const Maxwell3D& maxwell, std::size_t shade      viewport_flip[0] = regs.viewport_transform[0].scale_x < 0.0 ? -1.0f : 1.0f;      viewport_flip[1] = regs.viewport_transform[0].scale_y < 0.0 ? -1.0f : 1.0f; -    u32 func = static_cast<u32>(regs.alpha_test_func); +    auto func{static_cast<u32>(regs.alpha_test_func)};      // Normalize the gl variants of opCompare to be the same as the normal variants      const u32 op_gl_variant_base = static_cast<u32>(Maxwell3D::Regs::ComparisonOp::Never);      if (func >= op_gl_variant_base) { diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h index 37dcfefdb..cec18a832 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.h +++ b/src/video_core/renderer_opengl/gl_shader_manager.h @@ -4,6 +4,8 @@  #pragma once +#include <cstddef> +  #include <glad/glad.h>  #include "video_core/renderer_opengl/gl_resource_manager.h" @@ -38,55 +40,48 @@ static_assert(sizeof(MaxwellUniformData) < 16384,  class ProgramManager {  public: -    ProgramManager() { -        pipeline.Create(); -    } +    explicit ProgramManager(); +    ~ProgramManager(); + +    void ApplyTo(OpenGLState& state);      void UseProgrammableVertexShader(GLuint program) { -        vs = program; +        current_state.vertex_shader = program;      }      void UseProgrammableGeometryShader(GLuint program) { -        gs = program; +        current_state.geometry_shader = program;      }      void UseProgrammableFragmentShader(GLuint program) { -        fs = program; +        current_state.fragment_shader = program;      }      void UseTrivialGeometryShader() { -        gs = 0; -    } - -    void ApplyTo(OpenGLState& state) { -        UpdatePipeline(); -        state.draw.shader_program = 0; -        state.draw.program_pipeline = pipeline.handle; +        current_state.geometry_shader = 0;      }  private: -    void UpdatePipeline() { -        // Avoid updating the pipeline when values have no changed -        if (old_vs == vs && old_fs == fs && old_gs == gs) -            return; -        // Workaround for AMD bug -        glUseProgramStages(pipeline.handle, -                           GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | GL_FRAGMENT_SHADER_BIT, -                           0); - -        glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, vs); -        glUseProgramStages(pipeline.handle, GL_GEOMETRY_SHADER_BIT, gs); -        glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, fs); - -        // Update the old values -        old_vs = vs; -        old_fs = fs; -        old_gs = gs; -    } +    struct PipelineState { +        bool operator==(const PipelineState& rhs) const { +            return vertex_shader == rhs.vertex_shader && fragment_shader == rhs.fragment_shader && +                   geometry_shader == rhs.geometry_shader; +        } + +        bool operator!=(const PipelineState& rhs) const { +            return !operator==(rhs); +        } + +        GLuint vertex_shader{}; +        GLuint fragment_shader{}; +        GLuint geometry_shader{}; +    }; + +    void UpdatePipeline();      OGLPipeline pipeline; -    GLuint vs{}, fs{}, gs{}; -    GLuint old_vs{}, old_fs{}, old_gs{}; +    PipelineState current_state; +    PipelineState old_state;  };  } // namespace OpenGL::GLShader diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp index a7ac26d71..3b022a456 100644 --- a/src/video_core/surface.cpp +++ b/src/video_core/surface.cpp @@ -294,6 +294,8 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,          return PixelFormat::Z16;      case Tegra::Texture::TextureFormat::Z24S8:          return PixelFormat::Z24S8; +    case Tegra::Texture::TextureFormat::ZF32_X24S8: +        return PixelFormat::Z32FS8;      case Tegra::Texture::TextureFormat::DXT1:          return is_srgb ? PixelFormat::DXT1_SRGB : PixelFormat::DXT1;      case Tegra::Texture::TextureFormat::DXT23: diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 7438fbc0a..c29f2d2dc 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -1,6 +1,13 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. +  #include <QApplication>  #include <QHBoxLayout>  #include <QKeyEvent> +#include <QOffscreenSurface> +#include <QOpenGLWindow> +#include <QPainter>  #include <QScreen>  #include <QWindow>  #include <fmt/format.h> @@ -82,13 +89,36 @@ void EmuThread::run() {      render_window->moveContext();  } +class GGLContext : public Core::Frontend::GraphicsContext { +public: +    explicit GGLContext(QOpenGLContext* shared_context) : surface() { +        context = std::make_unique<QOpenGLContext>(shared_context); +        surface.setFormat(shared_context->format()); +        surface.create(); +    } + +    void MakeCurrent() override { +        context->makeCurrent(&surface); +    } + +    void DoneCurrent() override { +        context->doneCurrent(); +    } + +    void SwapBuffers() override {} + +private: +    std::unique_ptr<QOpenGLContext> context; +    QOffscreenSurface surface; +}; +  // This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL  // context.  // The corresponding functionality is handled in EmuThread instead -class GGLWidgetInternal : public QGLWidget { +class GGLWidgetInternal : public QOpenGLWindow {  public: -    GGLWidgetInternal(QGLFormat fmt, GRenderWindow* parent) -        : QGLWidget(fmt, parent), parent(parent) {} +    GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context) +        : QOpenGLWindow(shared_context), parent(parent) {}      void paintEvent(QPaintEvent* ev) override {          if (do_painting) { @@ -101,9 +131,51 @@ public:          parent->OnFramebufferSizeChanged();      } +    void keyPressEvent(QKeyEvent* event) override { +        InputCommon::GetKeyboard()->PressKey(event->key()); +    } + +    void keyReleaseEvent(QKeyEvent* event) override { +        InputCommon::GetKeyboard()->ReleaseKey(event->key()); +    } + +    void mousePressEvent(QMouseEvent* event) override { +        if (event->source() == Qt::MouseEventSynthesizedBySystem) +            return; // touch input is handled in TouchBeginEvent + +        const auto pos{event->pos()}; +        if (event->button() == Qt::LeftButton) { +            const auto [x, y] = parent->ScaleTouch(pos); +            parent->TouchPressed(x, y); +        } else if (event->button() == Qt::RightButton) { +            InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y()); +        } +    } + +    void mouseMoveEvent(QMouseEvent* event) override { +        if (event->source() == Qt::MouseEventSynthesizedBySystem) +            return; // touch input is handled in TouchUpdateEvent + +        const auto pos{event->pos()}; +        const auto [x, y] = parent->ScaleTouch(pos); +        parent->TouchMoved(x, y); +        InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y()); +    } + +    void mouseReleaseEvent(QMouseEvent* event) override { +        if (event->source() == Qt::MouseEventSynthesizedBySystem) +            return; // touch input is handled in TouchEndEvent + +        if (event->button() == Qt::LeftButton) +            parent->TouchReleased(); +        else if (event->button() == Qt::RightButton) +            InputCommon::GetMotionEmu()->EndTilt(); +    } +      void DisablePainting() {          do_painting = false;      } +      void EnablePainting() {          do_painting = true;      } @@ -114,7 +186,7 @@ private:  };  GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) -    : QWidget(parent), child(nullptr), emu_thread(emu_thread) { +    : QWidget(parent), child(nullptr), context(nullptr), emu_thread(emu_thread) {      setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")                         .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); @@ -137,19 +209,19 @@ void GRenderWindow::moveContext() {      auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr)                        ? emu_thread                        : qApp->thread(); -    child->context()->moveToThread(thread); +    context->moveToThread(thread);  }  void GRenderWindow::SwapBuffers() { -    // In our multi-threaded QGLWidget use case we shouldn't need to call `makeCurrent`, +    // In our multi-threaded QWidget use case we shouldn't need to call `makeCurrent`,      // since we never call `doneCurrent` in this thread.      // However:      // - The Qt debug runtime prints a bogus warning on the console if `makeCurrent` wasn't called      // since the last time `swapBuffers` was executed;      // - On macOS, if `makeCurrent` isn't called explicitely, resizing the buffer breaks. -    child->makeCurrent(); +    context->makeCurrent(child); -    child->swapBuffers(); +    context->swapBuffers(child);      if (!first_frame) {          emit FirstFrameDisplayed();          first_frame = true; @@ -157,11 +229,11 @@ void GRenderWindow::SwapBuffers() {  }  void GRenderWindow::MakeCurrent() { -    child->makeCurrent(); +    context->makeCurrent(child);  }  void GRenderWindow::DoneCurrent() { -    child->doneCurrent(); +    context->doneCurrent();  }  void GRenderWindow::PollEvents() {} @@ -174,14 +246,26 @@ void GRenderWindow::PollEvents() {}  void GRenderWindow::OnFramebufferSizeChanged() {      // Screen changes potentially incur a change in screen DPI, hence we should update the      // framebuffer size -    qreal pixelRatio = windowPixelRatio(); +    qreal pixelRatio = GetWindowPixelRatio();      unsigned width = child->QPaintDevice::width() * pixelRatio;      unsigned height = child->QPaintDevice::height() * pixelRatio;      UpdateCurrentFramebufferLayout(width, height);  } +void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) { +    if (child) { +        child->keyPressEvent(event); +    } +} + +void GRenderWindow::ForwardKeyReleaseEvent(QKeyEvent* event) { +    if (child) { +        child->keyReleaseEvent(event); +    } +} +  void GRenderWindow::BackupGeometry() { -    geometry = ((QGLWidget*)this)->saveGeometry(); +    geometry = ((QWidget*)this)->saveGeometry();  }  void GRenderWindow::RestoreGeometry() { @@ -199,18 +283,18 @@ QByteArray GRenderWindow::saveGeometry() {      // If we are a top-level widget, store the current geometry      // otherwise, store the last backup      if (parent() == nullptr) -        return ((QGLWidget*)this)->saveGeometry(); +        return ((QWidget*)this)->saveGeometry();      else          return geometry;  } -qreal GRenderWindow::windowPixelRatio() const { +qreal GRenderWindow::GetWindowPixelRatio() const {      // windowHandle() might not be accessible until the window is displayed to screen.      return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f;  }  std::pair<unsigned, unsigned> GRenderWindow::ScaleTouch(const QPointF pos) const { -    const qreal pixel_ratio = windowPixelRatio(); +    const qreal pixel_ratio = GetWindowPixelRatio();      return {static_cast<unsigned>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),              static_cast<unsigned>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};  } @@ -220,47 +304,6 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {      QWidget::closeEvent(event);  } -void GRenderWindow::keyPressEvent(QKeyEvent* event) { -    InputCommon::GetKeyboard()->PressKey(event->key()); -} - -void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { -    InputCommon::GetKeyboard()->ReleaseKey(event->key()); -} - -void GRenderWindow::mousePressEvent(QMouseEvent* event) { -    if (event->source() == Qt::MouseEventSynthesizedBySystem) -        return; // touch input is handled in TouchBeginEvent - -    auto pos = event->pos(); -    if (event->button() == Qt::LeftButton) { -        const auto [x, y] = ScaleTouch(pos); -        this->TouchPressed(x, y); -    } else if (event->button() == Qt::RightButton) { -        InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y()); -    } -} - -void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { -    if (event->source() == Qt::MouseEventSynthesizedBySystem) -        return; // touch input is handled in TouchUpdateEvent - -    auto pos = event->pos(); -    const auto [x, y] = ScaleTouch(pos); -    this->TouchMoved(x, y); -    InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y()); -} - -void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { -    if (event->source() == Qt::MouseEventSynthesizedBySystem) -        return; // touch input is handled in TouchEndEvent - -    if (event->button() == Qt::LeftButton) -        this->TouchReleased(); -    else if (event->button() == Qt::RightButton) -        InputCommon::GetMotionEmu()->EndTilt(); -} -  void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {      // TouchBegin always has exactly one touch point, so take the .first()      const auto [x, y] = ScaleTouch(event->touchPoints().first().pos()); @@ -313,35 +356,60 @@ void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) {      NotifyClientAreaSizeChanged(std::make_pair(width, height));  } +std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const { +    return std::make_unique<GGLContext>(shared_context.get()); +} +  void GRenderWindow::InitRenderTarget() { -    if (child) { -        delete child; -    } +    shared_context.reset(); +    context.reset(); -    if (layout()) { -        delete layout(); -    } +    delete child; +    child = nullptr; + +    delete container; +    container = nullptr; + +    delete layout();      first_frame = false;      // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,      // WA_DontShowOnScreen, WA_DeleteOnClose -    QGLFormat fmt; +    QSurfaceFormat fmt;      fmt.setVersion(4, 3); -    fmt.setProfile(QGLFormat::CoreProfile); +    fmt.setProfile(QSurfaceFormat::CoreProfile); +    // TODO: expose a setting for buffer value (ie default/single/double/triple) +    fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); +    shared_context = std::make_unique<QOpenGLContext>(); +    shared_context->setFormat(fmt); +    shared_context->create(); +    context = std::make_unique<QOpenGLContext>(); +    context->setShareContext(shared_context.get()); +    context->setFormat(fmt); +    context->create();      fmt.setSwapInterval(false); -    // Requests a forward-compatible context, which is required to get a 3.2+ context on OS X -    fmt.setOption(QGL::NoDeprecatedFunctions); +    child = new GGLWidgetInternal(this, shared_context.get()); +    container = QWidget::createWindowContainer(child, this); -    child = new GGLWidgetInternal(fmt, this);      QBoxLayout* layout = new QHBoxLayout(this); - -    resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); -    layout->addWidget(child); +    layout->addWidget(container);      layout->setMargin(0);      setLayout(layout); +    // Reset minimum size to avoid unwanted resizes when this function is called for a second time. +    setMinimumSize(1, 1); + +    // Show causes the window to actually be created and the OpenGL context as well, but we don't +    // want the widget to be shown yet, so immediately hide it. +    show(); +    hide(); + +    resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); +    child->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); +    container->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); +      OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);      OnFramebufferSizeChanged(); diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 3183621bc..9608b959f 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -7,9 +7,9 @@  #include <atomic>  #include <condition_variable>  #include <mutex> -#include <QGLWidget>  #include <QImage>  #include <QThread> +#include <QWidget>  #include "common/thread.h"  #include "core/core.h"  #include "core/frontend/emu_window.h" @@ -21,6 +21,8 @@ class QTouchEvent;  class GGLWidgetInternal;  class GMainWindow;  class GRenderWindow; +class QSurface; +class QOpenGLContext;  namespace VideoCore {  enum class LoadCallbackStage; @@ -121,25 +123,21 @@ public:      void MakeCurrent() override;      void DoneCurrent() override;      void PollEvents() override; +    std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; + +    void ForwardKeyPressEvent(QKeyEvent* event); +    void ForwardKeyReleaseEvent(QKeyEvent* event);      void BackupGeometry();      void RestoreGeometry();      void restoreGeometry(const QByteArray& geometry); // overridden      QByteArray saveGeometry();                        // overridden -    qreal windowPixelRatio() const; +    qreal GetWindowPixelRatio() const; +    std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const;      void closeEvent(QCloseEvent* event) override; - -    void keyPressEvent(QKeyEvent* event) override; -    void keyReleaseEvent(QKeyEvent* event) override; - -    void mousePressEvent(QMouseEvent* event) override; -    void mouseMoveEvent(QMouseEvent* event) override; -    void mouseReleaseEvent(QMouseEvent* event) override; -      bool event(QEvent* event) override; -      void focusOutEvent(QFocusEvent* event) override;      void OnClientAreaResized(unsigned width, unsigned height); @@ -161,7 +159,6 @@ signals:      void FirstFrameDisplayed();  private: -    std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const;      void TouchBeginEvent(const QTouchEvent* event);      void TouchUpdateEvent(const QTouchEvent* event);      void TouchEndEvent(); @@ -169,11 +166,17 @@ private:      void OnMinimalClientAreaChangeRequest(          const std::pair<unsigned, unsigned>& minimal_size) override; -    GGLWidgetInternal* child; +    QWidget* container = nullptr; +    GGLWidgetInternal* child = nullptr;      QByteArray geometry;      EmuThread* emu_thread; +    // Context that backs the GGLWidgetInternal (and will be used by core to render) +    std::unique_ptr<QOpenGLContext> context; +    // Context that will be shared between all newly created contexts. This should never be made +    // current +    std::unique_ptr<QOpenGLContext> shared_context;      /// Temporary storage of the screenshot taken      QImage screenshot_image; diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 802db3945..8725a78dc 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -394,6 +394,7 @@ void Config::ReadValues() {          ReadSetting("use_accurate_gpu_emulation", false).toBool();      Settings::values.use_asynchronous_gpu_emulation =          ReadSetting("use_asynchronous_gpu_emulation", false).toBool(); +    Settings::values.force_30fps_mode = ReadSetting("force_30fps_mode", false).toBool();      Settings::values.bg_red = ReadSetting("bg_red", 0.0).toFloat();      Settings::values.bg_green = ReadSetting("bg_green", 0.0).toFloat(); @@ -523,8 +524,8 @@ void Config::ReadValues() {      qt_config->beginGroup("Paths");      UISettings::values.roms_path = ReadSetting("romsPath").toString();      UISettings::values.symbols_path = ReadSetting("symbolsPath").toString(); -    UISettings::values.gamedir = ReadSetting("gameListRootDir", ".").toString(); -    UISettings::values.gamedir_deepscan = ReadSetting("gameListDeepScan", false).toBool(); +    UISettings::values.game_directory_path = ReadSetting("gameListRootDir", ".").toString(); +    UISettings::values.game_directory_deepscan = ReadSetting("gameListDeepScan", false).toBool();      UISettings::values.recent_files = ReadSetting("recentFiles").toStringList();      qt_config->endGroup(); @@ -664,6 +665,7 @@ void Config::SaveValues() {      WriteSetting("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation, false);      WriteSetting("use_asynchronous_gpu_emulation", Settings::values.use_asynchronous_gpu_emulation,                   false); +    WriteSetting("force_30fps_mode", Settings::values.force_30fps_mode, false);      // Cast to double because Qt's written float values are not human-readable      WriteSetting("bg_red", (double)Settings::values.bg_red, 0.0); @@ -768,8 +770,8 @@ void Config::SaveValues() {      WriteSetting("romsPath", UISettings::values.roms_path);      WriteSetting("symbolsPath", UISettings::values.symbols_path);      WriteSetting("screenshotPath", UISettings::values.screenshot_path); -    WriteSetting("gameListRootDir", UISettings::values.gamedir, "."); -    WriteSetting("gameListDeepScan", UISettings::values.gamedir_deepscan, false); +    WriteSetting("gameListRootDir", UISettings::values.game_directory_path, "."); +    WriteSetting("gameListDeepScan", UISettings::values.game_directory_deepscan, false);      WriteSetting("recentFiles", UISettings::values.recent_files);      qt_config->endGroup(); diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index eeb038afb..e48f4f5a3 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -28,7 +28,7 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)  ConfigureGeneral::~ConfigureGeneral() = default;  void ConfigureGeneral::setConfiguration() { -    ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan); +    ui->toggle_deepscan->setChecked(UISettings::values.game_directory_deepscan);      ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);      ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot);      ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); @@ -36,7 +36,7 @@ void ConfigureGeneral::setConfiguration() {  }  void ConfigureGeneral::applyConfiguration() { -    UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked(); +    UISettings::values.game_directory_deepscan = ui->toggle_deepscan->isChecked();      UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();      UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();      UISettings::values.theme = diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index dd1d67488..0a9883d37 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -77,6 +77,8 @@ void ConfigureGraphics::setConfiguration() {      ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation);      ui->use_asynchronous_gpu_emulation->setEnabled(!Core::System::GetInstance().IsPoweredOn());      ui->use_asynchronous_gpu_emulation->setChecked(Settings::values.use_asynchronous_gpu_emulation); +    ui->force_30fps_mode->setEnabled(!Core::System::GetInstance().IsPoweredOn()); +    ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode);      UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,                                                   Settings::values.bg_blue));  } @@ -90,6 +92,7 @@ void ConfigureGraphics::applyConfiguration() {      Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked();      Settings::values.use_asynchronous_gpu_emulation =          ui->use_asynchronous_gpu_emulation->isChecked(); +    Settings::values.force_30fps_mode = ui->force_30fps_mode->isChecked();      Settings::values.bg_red = static_cast<float>(bg_color.redF());      Settings::values.bg_green = static_cast<float>(bg_color.greenF());      Settings::values.bg_blue = static_cast<float>(bg_color.blueF()); diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index c6767e0ca..15ab18ecd 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -71,6 +71,13 @@           </widget>          </item>          <item> +         <widget class="QCheckBox" name="force_30fps_mode"> +          <property name="text"> +           <string>Force 30 FPS mode</string> +          </property> +         </widget> +        </item> +        <item>           <layout class="QHBoxLayout" name="horizontalLayout">            <item>             <widget class="QLabel" name="label"> diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 4b67656ac..b0ca766ec 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -467,9 +467,10 @@ void GameList::LoadInterfaceLayout() {  const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci", "nsp"};  void GameList::RefreshGameDirectory() { -    if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) { +    if (!UISettings::values.game_directory_path.isEmpty() && current_worker != nullptr) {          LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");          search_field->clear(); -        PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); +        PopulateAsync(UISettings::values.game_directory_path, +                      UISettings::values.game_directory_deepscan);      }  } diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index ca231d710..bdee44b04 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -213,7 +213,8 @@ GMainWindow::GMainWindow()      OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning);      game_list->LoadCompatibilityList(); -    game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); +    game_list->PopulateAsync(UISettings::values.game_directory_path, +                             UISettings::values.game_directory_deepscan);      // Show one-time "callout" messages to the user      ShowTelemetryCallout(); @@ -1278,8 +1279,8 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {          const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);          if (reload) { -            game_list->PopulateAsync(UISettings::values.gamedir, -                                     UISettings::values.gamedir_deepscan); +            game_list->PopulateAsync(UISettings::values.game_directory_path, +                                     UISettings::values.game_directory_deepscan);          }          config->Save(); @@ -1367,7 +1368,8 @@ void GMainWindow::OnMenuInstallToNAND() {      const auto success = [this]() {          QMessageBox::information(this, tr("Successfully Installed"),                                   tr("The file was successfully installed.")); -        game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); +        game_list->PopulateAsync(UISettings::values.game_directory_path, +                                 UISettings::values.game_directory_deepscan);      };      const auto failed = [this]() { @@ -1494,8 +1496,8 @@ void GMainWindow::OnMenuInstallToNAND() {  void GMainWindow::OnMenuSelectGameListRoot() {      QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));      if (!dir_path.isEmpty()) { -        UISettings::values.gamedir = dir_path; -        game_list->PopulateAsync(dir_path, UISettings::values.gamedir_deepscan); +        UISettings::values.game_directory_path = dir_path; +        game_list->PopulateAsync(dir_path, UISettings::values.game_directory_deepscan);      }  } @@ -1517,7 +1519,8 @@ void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target)                                                                        : FileUtil::UserPath::NANDDir,                                dir_path.toStdString());          Service::FileSystem::CreateFactories(*vfs); -        game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); +        game_list->PopulateAsync(UISettings::values.game_directory_path, +                                 UISettings::values.game_directory_deepscan);      }  } @@ -1669,8 +1672,8 @@ void GMainWindow::OnConfigure() {          const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);          if (reload) { -            game_list->PopulateAsync(UISettings::values.gamedir, -                                     UISettings::values.gamedir_deepscan); +            game_list->PopulateAsync(UISettings::values.game_directory_path, +                                     UISettings::values.game_directory_deepscan);          }          config->Save(); @@ -1920,7 +1923,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {      Service::FileSystem::CreateFactories(*vfs);      if (behavior == ReinitializeKeyBehavior::Warning) { -        game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); +        game_list->PopulateAsync(UISettings::values.game_directory_path, +                                 UISettings::values.game_directory_deepscan);      }  } @@ -2027,6 +2031,18 @@ void GMainWindow::dragMoveEvent(QDragMoveEvent* event) {      event->acceptProposedAction();  } +void GMainWindow::keyPressEvent(QKeyEvent* event) { +    if (render_window) { +        render_window->ForwardKeyPressEvent(event); +    } +} + +void GMainWindow::keyReleaseEvent(QKeyEvent* event) { +    if (render_window) { +        render_window->ForwardKeyReleaseEvent(event); +    } +} +  bool GMainWindow::ConfirmChangeGame() {      if (emu_thread == nullptr)          return true; @@ -2094,7 +2110,8 @@ int main(int argc, char* argv[]) {      QCoreApplication::setOrganizationName("yuzu team");      QCoreApplication::setApplicationName("yuzu"); -    QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); +    // Enables the core to make the qt created contexts current on std::threads +    QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);      QApplication app(argc, argv);      // Qt changes the locale and causes issues in float conversion using std::to_string() when diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 85e3810f2..ce5045819 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -252,4 +252,8 @@ protected:      void dropEvent(QDropEvent* event) override;      void dragEnterEvent(QDragEnterEvent* event) override;      void dragMoveEvent(QDragMoveEvent* event) override; + +    // Overrides used to forward signals to the render window when the focus moves out. +    void keyPressEvent(QKeyEvent* event) override; +    void keyReleaseEvent(QKeyEvent* event) override;  }; diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h index 45e705b61..dbd318e20 100644 --- a/src/yuzu/ui_settings.h +++ b/src/yuzu/ui_settings.h @@ -55,8 +55,8 @@ struct Values {      QString roms_path;      QString symbols_path;      QString screenshot_path; -    QString gamedir; -    bool gamedir_deepscan; +    QString game_directory_path; +    bool game_directory_deepscan;      QStringList recent_files;      QString theme; diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index de7a26e14..68a176032 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -19,6 +19,37 @@  #include "input_common/sdl/sdl.h"  #include "yuzu_cmd/emu_window/emu_window_sdl2.h" +class SDLGLContext : public Core::Frontend::GraphicsContext { +public: +    explicit SDLGLContext() { +        // create a hidden window to make the shared context against +        window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, // x position +                                  SDL_WINDOWPOS_UNDEFINED,     // y position +                                  Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height, +                                  SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN); +        context = SDL_GL_CreateContext(window); +    } + +    ~SDLGLContext() { +        SDL_GL_DeleteContext(context); +        SDL_DestroyWindow(window); +    } + +    void MakeCurrent() override { +        SDL_GL_MakeCurrent(window, context); +    } + +    void DoneCurrent() override { +        SDL_GL_MakeCurrent(window, nullptr); +    } + +    void SwapBuffers() override {} + +private: +    SDL_Window* window; +    SDL_GLContext context; +}; +  void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {      TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));      InputCommon::GetMotionEmu()->Tilt(x, y); @@ -153,6 +184,7 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {      SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);      SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);      SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); +    SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);      std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_fullname,                                             Common::g_scm_branch, Common::g_scm_desc); @@ -171,7 +203,6 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {      if (fullscreen) {          Fullscreen();      } -      gl_context = SDL_GL_CreateContext(render_window);      if (gl_context == nullptr) { @@ -278,3 +309,7 @@ void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(      SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second);  } + +std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2::CreateSharedContext() const { +    return std::make_unique<SDLGLContext>(); +} diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h index b0d4116cc..17e98227f 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h @@ -27,6 +27,8 @@ public:      /// Releases the GL context from the caller thread      void DoneCurrent() override; +    std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; +      /// Whether the window is still open, and a close request hasn't yet been sent      bool IsOpen() const;  | 
