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