diff --git a/CMakeLists.txt b/CMakeLists.txt index 4da85221d0..1bed3ec51a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1070,6 +1070,7 @@ add_library(native STATIC ext/native/util/text/wrap_text.h ext/native/util/text/wrap_text.cpp ext/native/util/const_map.h + ext/native/util/tiny_set.h ext/native/ext/jpge/jpgd.cpp ext/native/ext/jpge/jpgd.h ext/native/ext/jpge/jpge.cpp diff --git a/Common/Arm64Emitter.cpp b/Common/Arm64Emitter.cpp index 3a18a308a2..e3e148a60a 100644 --- a/Common/Arm64Emitter.cpp +++ b/Common/Arm64Emitter.cpp @@ -275,10 +275,14 @@ static int EncodeSize(int size) { } } -void ARM64XEmitter::SetCodePointer(u8* ptr) +ARM64XEmitter::ARM64XEmitter(const u8 *ptr, u8 *writePtr) { + SetCodePointer(ptr, writePtr); +} + +void ARM64XEmitter::SetCodePointer(const u8 *ptr, u8 *writePtr) { m_code = ptr; - m_startcode = m_code; + m_writable = writePtr; m_lastCacheFlushEnd = ptr; } @@ -289,7 +293,7 @@ const u8* ARM64XEmitter::GetCodePointer() const u8* ARM64XEmitter::GetWritableCodePtr() { - return m_code; + return m_writable; } void ARM64XEmitter::ReserveCodeSpace(u32 bytes) @@ -321,11 +325,11 @@ void ARM64XEmitter::FlushIcache() m_lastCacheFlushEnd = m_code; } -void ARM64XEmitter::FlushIcacheSection(u8* start, u8* end) +void ARM64XEmitter::FlushIcacheSection(const u8 *start, const u8 *end) { #if defined(IOS) // Header file says this is equivalent to: sys_icache_invalidate(start, end - start); - sys_cache_control(kCacheFunctionPrepareForExecution, start, end - start); + sys_cache_control(kCacheFunctionPrepareForExecution, (void *)start, end - start); #elif PPSSPP_PLATFORM(WINDOWS) FlushInstructionCache(GetCurrentProcess(), start, end - start); #elif PPSSPP_ARCH(ARM64) @@ -911,7 +915,7 @@ void ARM64XEmitter::SetJumpTarget(FixupBranch const& branch) inst = ((branch.bit & 0x20) << 26) | (0x1B << 25) | (Not << 24) | ((branch.bit & 0x1F) << 19) | (MaskImm14(distance) << 5) | reg; } break; - case 5: // B (uncoditional) + case 5: // B (unconditional) _assert_msg_(DYNA_REC, IsInRangeImm26(distance), "%s(%d): Received too large distance: %llx", __FUNCTION__, branch.type, distance); inst = (0x5 << 26) | MaskImm26(distance); break; @@ -920,7 +924,9 @@ void ARM64XEmitter::SetJumpTarget(FixupBranch const& branch) inst = (0x25 << 26) | MaskImm26(distance); break; } - *(u32*)branch.ptr = inst; + + ptrdiff_t writable = m_writable - m_code; + *(u32 *)(branch.ptr + writable) = inst; } FixupBranch ARM64XEmitter::CBZ(ARM64Reg Rt) @@ -3924,8 +3930,11 @@ void ARM64XEmitter::SUBSI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) } void ARM64CodeBlock::PoisonMemory(int offset) { - u32* ptr = (u32*)(region + offset); - u32* maxptr = (u32*)(region + region_size - offset); + // So we can adjust region to writable space. Might be zero. + ptrdiff_t writable = m_writable - m_code; + + u32 *ptr = (u32 *)(region + offset + writable); + u32 *maxptr = (u32 *)(region + region_size - offset + writable); // If our memory isn't a multiple of u32 then this won't write the last remaining bytes with anything // Less than optimal, but there would be nothing we could do but throw a runtime warning anyway. // AArch64: 0xD4200000 = BRK 0 diff --git a/Common/Arm64Emitter.h b/Common/Arm64Emitter.h index a6ce09b3fa..eb0fe1f1e5 100644 --- a/Common/Arm64Emitter.h +++ b/Common/Arm64Emitter.h @@ -162,7 +162,8 @@ enum RoundingMode { struct FixupBranch { - u8* ptr; + // Pointer to executable code address. + const u8 *ptr; // Type defines // 0 = CBZ (32bit) // 1 = CBNZ (32bit) @@ -338,11 +339,12 @@ public: class ARM64XEmitter { friend class ARM64FloatEmitter; + friend class ARM64CodeBlock; private: - u8* m_code; - u8* m_startcode; - u8* m_lastCacheFlushEnd; + const u8 *m_code = nullptr; + u8 *m_writable = nullptr; + const u8 *m_lastCacheFlushEnd = nullptr; void EncodeCompareBranchInst(u32 op, ARM64Reg Rt, const void* ptr); void EncodeTestBranchInst(u32 op, ARM64Reg Rt, u8 bits, const void* ptr); @@ -376,34 +378,30 @@ private: protected: inline void Write32(u32 value) { - *(u32*)m_code = value; + *(u32 *)m_writable = value; m_code += 4; + m_writable += 4; } public: ARM64XEmitter() - : m_code(nullptr), m_startcode(nullptr), m_lastCacheFlushEnd(nullptr) { } - ARM64XEmitter(u8* code_ptr) { - m_code = code_ptr; - m_lastCacheFlushEnd = code_ptr; - m_startcode = code_ptr; - } + ARM64XEmitter(const u8 *codePtr, u8 *writablePtr); virtual ~ARM64XEmitter() { } - void SetCodePointer(u8* ptr); + void SetCodePointer(const u8 *ptr, u8 *writePtr); const u8* GetCodePointer() const; void ReserveCodeSpace(u32 bytes); const u8* AlignCode16(); const u8* AlignCodePage(); void FlushIcache(); - void FlushIcacheSection(u8* start, u8* end); + void FlushIcacheSection(const u8* start, const u8* end); u8* GetWritableCodePtr(); // FixupBranch branching diff --git a/Common/ArmEmitter.cpp b/Common/ArmEmitter.cpp index 8626cdc434..dcaab522c5 100644 --- a/Common/ArmEmitter.cpp +++ b/Common/ArmEmitter.cpp @@ -586,7 +586,7 @@ void ARMXEmitter::QuickCallFunction(ARMReg reg, const void *func) { } } -void ARMXEmitter::SetCodePointer(u8 *ptr) +void ARMXEmitter::SetCodePointer(u8 *ptr, u8 *writePtr) { code = ptr; startcode = code; diff --git a/Common/ArmEmitter.h b/Common/ArmEmitter.h index 85c2d916b7..b8116129ec 100644 --- a/Common/ArmEmitter.h +++ b/Common/ArmEmitter.h @@ -443,7 +443,7 @@ public: } virtual ~ARMXEmitter() {} - void SetCodePointer(u8 *ptr); + void SetCodePointer(u8 *ptr, u8 *writePtr); const u8 *GetCodePointer() const; void ReserveCodeSpace(u32 bytes); diff --git a/Common/Atomic_GCC.h b/Common/Atomic_GCC.h deleted file mode 100644 index c4c2c4f0f5..0000000000 --- a/Common/Atomic_GCC.h +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project -// Licensed under GPLv2 -// Refer to the license.txt file included. - -// IWYU pragma: private, include "Common/Atomic.h" - -#pragma once - -#include "CommonTypes.h" - -// Atomic operations are performed in a single step by the CPU. It is -// impossible for other threads to see the operation "half-done." -// -// Some atomic operations can be combined with different types of memory -// barriers called "Acquire semantics" and "Release semantics", defined below. -// -// Acquire semantics: Future memory accesses cannot be relocated to before the -// operation. -// -// Release semantics: Past memory accesses cannot be relocated to after the -// operation. -// -// These barriers affect not only the compiler, but also the CPU. - -namespace Common -{ - -inline void AtomicAdd(volatile u32& target, u32 value) -{ - __sync_add_and_fetch(&target, value); -} - -inline void AtomicAnd(volatile u32& target, u32 value) -{ - __sync_and_and_fetch(&target, value); -} - -inline void AtomicDecrement(volatile u32& target) -{ - __sync_add_and_fetch(&target, -1); -} - -inline void AtomicIncrement(volatile u32& target) -{ - __sync_add_and_fetch(&target, 1); -} - -inline void AtomicOr(volatile u32& target, u32 value) -{ - __sync_or_and_fetch(&target, value); -} - -// Support clang versions older than 3.4. -#if __clang__ -#if !__has_feature(cxx_atomic) -template -_Atomic(T)* ToC11Atomic(volatile T* loc) -{ - return (_Atomic(T)*) loc; -} - -#define __atomic_load_n(p, m) __c11_atomic_load(ToC11Atomic(p), m) -#define __atomic_store_n(p, v, m) __c11_atomic_store(ToC11Atomic(p), v, m) -#define __atomic_exchange_n(p, v, m) __c11_atomic_exchange(ToC11Atomic(p), v, m) -#endif -#endif - -#ifndef __ATOMIC_RELAXED -#error __ATOMIC_RELAXED not defined; your compiler version is too old. -#endif - -template -inline T AtomicLoad(volatile T& src) -{ - return __atomic_load_n(&src, __ATOMIC_RELAXED); -} - -template -inline T AtomicLoadAcquire(volatile T& src) -{ - return __atomic_load_n(&src, __ATOMIC_ACQUIRE); -} - -template -inline void AtomicStore(volatile T& dest, U value) -{ - __atomic_store_n(&dest, value, __ATOMIC_RELAXED); -} - -template -inline void AtomicStoreRelease(volatile T& dest, U value) -{ - __atomic_store_n(&dest, value, __ATOMIC_RELEASE); -} - -template -inline T* AtomicExchangeAcquire(T* volatile& loc, U newval) -{ - return __atomic_exchange_n(&loc, newval, __ATOMIC_ACQ_REL); -} - -} diff --git a/Common/Atomic_Win32.h b/Common/Atomic_Win32.h deleted file mode 100644 index 7040deb809..0000000000 --- a/Common/Atomic_Win32.h +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project -// Licensed under GPLv2 -// Refer to the license.txt file included. - -// IWYU pragma: private, include "Common/Atomic.h" - -#pragma once - -#include - -#include "CommonTypes.h" -#include "CommonWindows.h" - -// Atomic operations are performed in a single step by the CPU. It is -// impossible for other threads to see the operation "half-done." -// -// Some atomic operations can be combined with different types of memory -// barriers called "Acquire semantics" and "Release semantics", defined below. -// -// Acquire semantics: Future memory accesses cannot be relocated to before the -// operation. -// -// Release semantics: Past memory accesses cannot be relocated to after the -// operation. -// -// These barriers affect not only the compiler, but also the CPU. -// -// NOTE: Acquire and Release are not differentiated right now. They perform a -// full memory barrier instead of a "one-way" memory barrier. The newest -// Windows SDK has Acquire and Release versions of some Interlocked* functions. - -namespace Common -{ - -inline void AtomicAdd(volatile u32& target, u32 value) -{ - _InterlockedExchangeAdd((volatile LONG*)&target, (LONG)value); -} - -inline void AtomicAnd(volatile u32& target, u32 value) -{ - _InterlockedAnd((volatile LONG*)&target, (LONG)value); -} - -inline void AtomicIncrement(volatile u32& target) -{ - _InterlockedIncrement((volatile LONG*)&target); -} - -inline void AtomicDecrement(volatile u32& target) -{ - _InterlockedDecrement((volatile LONG*)&target); -} - -inline void AtomicOr(volatile u32& target, u32 value) -{ - _InterlockedOr((volatile LONG*)&target, (LONG)value); -} - -// For the comment below to hold, better only use this with 32-bit types.. -template -inline T AtomicLoad(volatile T& src) -{ - return src; // 32-bit reads are always atomic. -} - -// For the comment below to hold, better only use this with 32-bit types.. -template -inline T AtomicLoadAcquire(volatile T& src) -{ - T result = src; // 32-bit reads are always atomic. - _ReadBarrier(); // Compiler instruction only. x86 loads always have acquire semantics. - return result; -} - -// For the comment below to hold, better only use this with 32-bit types.. -template -inline void AtomicStore(volatile T& dest, U value) -{ - dest = (T)value; // 32-bit writes are always atomic. -} - -// For the comment below to hold, better only use this with 32-bit types.. -template -inline void AtomicStoreRelease(volatile T& dest, U value) -{ - _WriteBarrier(); // Compiler instruction only. x86 stores always have release semantics. - dest = (T)value; // 32-bit writes are always atomic -} - -#ifdef __MINGW32__ -void *_InterlockedExchangePointer_acq(void *volatile *, void volatile *); -#endif - -template -inline T* AtomicExchangeAcquire(T* volatile& loc, U newval) -{ - return (T*)_InterlockedExchangePointer_acq((void* volatile*)&loc, (void*)newval); -} - -} diff --git a/Common/Atomics.h b/Common/Atomics.h deleted file mode 100644 index bdf2161d7e..0000000000 --- a/Common/Atomics.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) 2003 Dolphin Project. - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License 2.0 for more details. - -// A copy of the GPL 2.0 should have been included with the program. -// If not, see http://www.gnu.org/licenses/ - -// Official SVN repository and contact information can be found at -// http://code.google.com/p/dolphin-emu/ - -#pragma once - -#ifdef _WIN32 - -#include "Atomic_Win32.h" - -#else - -// GCC-compatible compiler assumed! -#include "Atomic_GCC.h" - -#endif diff --git a/Common/CodeBlock.h b/Common/CodeBlock.h index a0f5dd55f4..f60c4b9305 100644 --- a/Common/CodeBlock.h +++ b/Common/CodeBlock.h @@ -4,6 +4,7 @@ #pragma once +#include #include "Common.h" #include "MemoryUtil.h" @@ -15,14 +16,13 @@ class CodeBlockCommon { public: - CodeBlockCommon() : region(nullptr), region_size(0) {} + CodeBlockCommon() {} virtual ~CodeBlockCommon() {} bool IsInSpace(const u8 *ptr) { return (ptr >= region) && (ptr < (region + region_size)); } - virtual void SetCodePtr(u8 *ptr) = 0; virtual const u8 *GetCodePtr() const = 0; u8 *GetBasePtr() { @@ -33,9 +33,15 @@ public: return ptr - region; } + virtual const u8 *GetCodePtrFromWritablePtr(u8 *ptr) = 0; + virtual u8 *GetWritablePtrFromCodePtr(const u8 *ptr) = 0; + protected: - u8 *region; - size_t region_size; + virtual void SetCodePtr(u8 *ptr) = 0; + + // Note: this should be the readable/executable side if writable is a different pointer. + u8 *region = nullptr; + size_t region_size = 0; }; template class CodeBlock : public CodeBlockCommon, public T { @@ -48,15 +54,16 @@ private: virtual void PoisonMemory(int offset) = 0; public: - CodeBlock() : writeStart_(nullptr) {} + CodeBlock() {} virtual ~CodeBlock() { if (region) FreeCodeSpace(); } // Call this before you generate any code. void AllocCodeSpace(int size) { region_size = size; // The protection will be set to RW if PlatformIsWXExclusive. - region = (u8*)AllocateExecutableMemory(region_size); - T::SetCodePointer(region); + region = (u8 *)AllocateExecutableMemory(region_size); + writableRegion = region; + T::SetCodePointer(region, writableRegion); } // Always clear code space with breakpoints, so that if someone accidentally executes @@ -104,26 +111,42 @@ public: ProtectMemoryPages(region, region_size, MEM_PROT_READ | MEM_PROT_WRITE); FreeMemoryPages(region, region_size); region = nullptr; + writableRegion = nullptr; region_size = 0; } - void SetCodePtr(u8 *ptr) override { - T::SetCodePointer(ptr); - } - const u8 *GetCodePtr() const override { return T::GetCodePointer(); } - void ResetCodePtr(int offset) { - T::SetCodePointer(region + offset); + void ResetCodePtr(size_t offset) { + T::SetCodePointer(region + offset, writableRegion + offset); } size_t GetSpaceLeft() const { return region_size - (T::GetCodePointer() - region); } + const u8 *GetCodePtrFromWritablePtr(u8 *ptr) override { + // So we can adjust region to writable space. Might be zero. + ptrdiff_t writable = T::GetWritableCodePtr() - T::GetCodePointer(); + return ptr - writable; + } + + u8 *GetWritablePtrFromCodePtr(const u8 *ptr) override { + // So we can adjust region to writable space. Might be zero. + ptrdiff_t writable = T::GetWritableCodePtr() - T::GetCodePointer(); + return (u8 *)ptr + writable; + } + +protected: + void SetCodePtr(u8 *ptr) override { + T::SetCodePointer(ptr, GetWritablePtrFromCodePtr(ptr)); + } + private: - const uint8_t *writeStart_; + // Note: this is a readable pointer. + const uint8_t *writeStart_ = nullptr; + uint8_t *writableRegion = nullptr; }; diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index 037f4258ea..ddc0fd6869 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -384,9 +384,6 @@ - - - diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters index 65547b8502..9a413fe9a8 100644 --- a/Common/Common.vcxproj.filters +++ b/Common/Common.vcxproj.filters @@ -3,9 +3,6 @@ - - - diff --git a/Common/ConsoleListener.cpp b/Common/ConsoleListener.cpp index 2d2851d905..18472eaa07 100644 --- a/Common/ConsoleListener.cpp +++ b/Common/ConsoleListener.cpp @@ -15,7 +15,7 @@ // Official SVN repository and contact information can be found at // http://code.google.com/p/dolphin-emu/ - +#include #include // min #include // System: To be able to add strings with "+" #include @@ -35,7 +35,6 @@ #include "util/text/utf8.h" #include "Common.h" #include "ConsoleListener.h" // Common -#include "Atomics.h" #if defined(USING_WIN_UI) const int LOG_PENDING_MAX = 120 * 10000; @@ -49,8 +48,8 @@ HANDLE ConsoleListener::hTriggerEvent = NULL; CRITICAL_SECTION ConsoleListener::criticalSection; char *ConsoleListener::logPending = NULL; -volatile u32 ConsoleListener::logPendingReadPos = 0; -volatile u32 ConsoleListener::logPendingWritePos = 0; +std::atomic ConsoleListener::logPendingReadPos; +std::atomic ConsoleListener::logPendingWritePos; #endif ConsoleListener::ConsoleListener() : bHidden(true) @@ -188,7 +187,7 @@ void ConsoleListener::Close() { if (hThread != NULL) { - Common::AtomicStoreRelease(logPendingWritePos, (u32) -1); + logPendingWritePos.store((u32)-1, std::memory_order_release); SetEvent(hTriggerEvent); WaitForSingleObject(hThread, LOG_SHUTDOWN_DELAY_MS); @@ -315,7 +314,7 @@ void ConsoleListener::LogWriterThread() WaitForSingleObject(hTriggerEvent, INFINITE); Sleep(LOG_LATENCY_DELAY_MS); - u32 logRemotePos = Common::AtomicLoadAcquire(logPendingWritePos); + u32 logRemotePos = logPendingWritePos.load(std::memory_order_acquire); if (logRemotePos == (u32) -1) break; else if (logRemotePos == logPendingReadPos) @@ -323,7 +322,7 @@ void ConsoleListener::LogWriterThread() else { EnterCriticalSection(&criticalSection); - logRemotePos = Common::AtomicLoadAcquire(logPendingWritePos); + logRemotePos = logPendingWritePos.load(std::memory_order_acquire); int start = 0; if (logRemotePos < logPendingReadPos) @@ -398,7 +397,7 @@ void ConsoleListener::SendToThread(LogTypes::LOG_LEVELS Level, const char *Text) } EnterCriticalSection(&criticalSection); - u32 logWritePos = Common::AtomicLoad(logPendingWritePos); + u32 logWritePos = logPendingWritePos.load(); u32 prevLogWritePos = logWritePos; if (logWritePos + ColorLen + Len >= LOG_PENDING_MAX) { @@ -448,7 +447,7 @@ void ConsoleListener::SendToThread(LogTypes::LOG_LEVELS Level, const char *Text) return; } - Common::AtomicStoreRelease(logPendingWritePos, logWritePos); + logPendingWritePos.store(logWritePos, std::memory_order::memory_order_release); LeaveCriticalSection(&criticalSection); SetEvent(hTriggerEvent); diff --git a/Common/ConsoleListener.h b/Common/ConsoleListener.h index e90654ed65..66a612a849 100644 --- a/Common/ConsoleListener.h +++ b/Common/ConsoleListener.h @@ -17,6 +17,8 @@ #pragma once +#include + #include "LogManager.h" #ifdef _WIN32 @@ -44,6 +46,7 @@ public: void Show(bool bShow); bool Hidden() const { return bHidden; } + private: #if defined(USING_WIN_UI) HWND hWnd; @@ -60,8 +63,8 @@ private: static CRITICAL_SECTION criticalSection; static char *logPending; - static volatile u32 logPendingReadPos; - static volatile u32 logPendingWritePos; + static std::atomic logPendingReadPos; + static std::atomic logPendingWritePos; int openWidth_; int openHeight_; diff --git a/Common/Vulkan/VulkanContext.cpp b/Common/Vulkan/VulkanContext.cpp index 501690e861..b901388a1c 100644 --- a/Common/Vulkan/VulkanContext.cpp +++ b/Common/Vulkan/VulkanContext.cpp @@ -36,15 +36,7 @@ #endif static const char *validationLayers[] = { - "VK_LAYER_LUNARG_standard_validation", - /* - "VK_LAYER_GOOGLE_threading", - "VK_LAYER_LUNARG_draw_state", - "VK_LAYER_LUNARG_image", - "VK_LAYER_LUNARG_mem_tracker", - "VK_LAYER_LUNARG_object_tracker", - "VK_LAYER_LUNARG_param_checker", - */ + "VK_LAYER_KHRONOS_validation", /* // For layers included in the Android NDK. "VK_LAYER_GOOGLE_threading", diff --git a/Common/x64Emitter.cpp b/Common/x64Emitter.cpp index 539fc7eeae..df14332f13 100644 --- a/Common/x64Emitter.cpp +++ b/Common/x64Emitter.cpp @@ -96,7 +96,7 @@ enum NormalSSEOps }; -void XEmitter::SetCodePointer(u8 *ptr) +void XEmitter::SetCodePointer(u8 *ptr, u8 *writePtr) { code = ptr; } diff --git a/Common/x64Emitter.h b/Common/x64Emitter.h index 87ae096bb1..30309f71ba 100644 --- a/Common/x64Emitter.h +++ b/Common/x64Emitter.h @@ -372,7 +372,7 @@ public: void WriteModRM(int mod, int rm, int reg); void WriteSIB(int scale, int index, int base); - void SetCodePointer(u8 *ptr); + void SetCodePointer(u8 *ptr, u8 *writePtr); const u8 *GetCodePointer() const; void ReserveCodeSpace(int bytes); diff --git a/Core/Config.cpp b/Core/Config.cpp index 2da5931c8a..2b4680bf5a 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -462,6 +462,12 @@ static ConfigSetting generalSettings[] = { ConfigSetting("GridView2", &g_Config.bGridView2, true), ConfigSetting("GridView3", &g_Config.bGridView3, false), ConfigSetting("ComboMode", &g_Config.iComboMode, 0), + ConfigSetting("RightAnalogUp", &g_Config.iRightAnalogUp, 0), + ConfigSetting("RightAnalogDown", &g_Config.iRightAnalogDown, 0), + ConfigSetting("RightAnalogLeft", &g_Config.iRightAnalogLeft, 0), + ConfigSetting("RightAnalogRight", &g_Config.iRightAnalogRight, 0), + ConfigSetting("RightAnalogPress", &g_Config.iRightAnalogPress, 0), + ConfigSetting("RightAnalogCustom", &g_Config.bRightAnalogCustom, false), // "default" means let emulator decide, "" means disable. ConfigSetting("ReportingHost", &g_Config.sReportHost, "default"), @@ -795,7 +801,6 @@ static ConfigSetting soundSettings[] = { ConfigSetting("Enable", &g_Config.bEnableSound, true, true, true), ConfigSetting("AudioBackend", &g_Config.iAudioBackend, 0, true, true), ConfigSetting("ExtraAudioBuffering", &g_Config.bExtraAudioBuffering, false, true, false), - ConfigSetting("AudioResampler", &g_Config.bAudioResampler, true, true, true), ConfigSetting("GlobalVolume", &g_Config.iGlobalVolume, VOLUME_MAX, true, true), ConfigSetting("SASVolume", &g_Config.iSASVolume, 8, true, true), ConfigSetting("ATRACMP3Volume", &g_Config.iATRACMP3Volume, 8, true, true), @@ -1206,6 +1211,12 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) { } } + auto postShaderSetting = iniFile.GetOrCreateSection("PostShaderSetting")->ToMap(); + mPostShaderSetting.clear(); + for (auto it : postShaderSetting) { + mPostShaderSetting[it.first] = std::stof(it.second); + } + // This caps the exponent 4 (so 16x.) if (iAnisotropyLevel > 4) { iAnisotropyLevel = 4; @@ -1317,6 +1328,14 @@ void Config::Save(const char *saveReason) { pinnedPaths->Set(keyName, vPinnedPaths[i]); } + if (!bGameSpecific) { + IniFile::Section *postShaderSetting = iniFile.GetOrCreateSection("PostShaderSetting"); + postShaderSetting->Clear(); + for (auto it = mPostShaderSetting.begin(), end = mPostShaderSetting.end(); it != end; ++it) { + postShaderSetting->Set(it->first.c_str(), it->second); + } + } + IniFile::Section *control = iniFile.GetOrCreateSection("Control"); control->Delete("DPadRadius"); @@ -1566,6 +1585,12 @@ bool Config::saveGameConfig(const std::string &pGameId, const std::string &title } }); + IniFile::Section *postShaderSetting = iniFile.GetOrCreateSection("PostShaderSetting"); + postShaderSetting->Clear(); + for (auto it = mPostShaderSetting.begin(), end = mPostShaderSetting.end(); it != end; ++it) { + postShaderSetting->Set(it->first.c_str(), it->second); + } + KeyMap::SaveToIni(iniFile); iniFile.Save(fullIniFilePath); @@ -1584,6 +1609,12 @@ bool Config::loadGameConfig(const std::string &pGameId, const std::string &title IniFile iniFile; iniFile.Load(iniFileNameFull); + auto postShaderSetting = iniFile.GetOrCreateSection("PostShaderSetting")->ToMap(); + mPostShaderSetting.clear(); + for (auto it : postShaderSetting) { + mPostShaderSetting[it.first] = std::stof(it.second); + } + IterateSettings(iniFile, [](IniFile::Section *section, ConfigSetting *setting) { if (setting->perGame_) { setting->Get(section); @@ -1608,6 +1639,12 @@ void Config::unloadGameConfig() { } }); + auto postShaderSetting = iniFile.GetOrCreateSection("PostShaderSetting")->ToMap(); + mPostShaderSetting.clear(); + for (auto it : postShaderSetting) { + mPostShaderSetting[it.first] = std::stof(it.second); + } + LoadStandardControllerIni(); } } diff --git a/Core/Config.h b/Core/Config.h index bc719ae38c..c92119b1b3 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -214,6 +214,7 @@ public: int iSplineBezierQuality; // 0 = low , 1 = Intermediate , 2 = High bool bHardwareTessellation; std::string sPostShaderName; // Off for off. + std::map mPostShaderSetting; bool bGfxDebugOutput; bool bGfxDebugSplitSubmit; int iInflightFrames; @@ -269,7 +270,6 @@ public: bool bShowDebugStats; bool bShowAudioDebug; bool bShowGpuProfile; - bool bAudioResampler; //Analog stick tilting //the base x and y tilt. this inclination is treated as (0,0) and the tilt input @@ -296,6 +296,14 @@ public: //Combo key screen flag int iComboMode; + // Right analog binding + int iRightAnalogUp; + int iRightAnalogDown; + int iRightAnalogLeft; + int iRightAnalogRight; + int iRightAnalogPress; + bool bRightAnalogCustom; + // Disable diagonals bool bDisableDpadDiagonals; bool bGamepadOnlyFocused; diff --git a/Core/CoreTiming.cpp b/Core/CoreTiming.cpp index 743c01dc22..2f0016d511 100644 --- a/Core/CoreTiming.cpp +++ b/Core/CoreTiming.cpp @@ -15,7 +15,7 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. - +#include #include #include #include @@ -24,7 +24,6 @@ #include "profiler/profiler.h" #include "Common/MsgHandler.h" -#include "Common/Atomics.h" #include "Core/CoreTiming.h" #include "Core/Core.h" #include "Core/Config.h" @@ -76,7 +75,7 @@ Event *eventPool = 0; Event *eventTsPool = 0; int allocatedTsEvents = 0; // Optimization to skip MoveEvents when possible. -volatile u32 hasTsEvents = 0; +std::atomic hasTsEvents; // Downcount has been moved to currentMIPS, to save a couple of clocks in every ARM JIT block // as we can already reach that structure through a register. @@ -255,7 +254,7 @@ void ScheduleEvent_Threadsafe(s64 cyclesIntoFuture, int event_type, u64 userdata tsLast->next = ne; tsLast = ne; - Common::AtomicStoreRelease(hasTsEvents, 1); + hasTsEvents.store(1, std::memory_order::memory_order_release); } // Same as ScheduleEvent_Threadsafe(0, ...) EXCEPT if we are already on the CPU thread @@ -535,7 +534,7 @@ void ProcessFifoWaitEvents() void MoveEvents() { - Common::AtomicStoreRelease(hasTsEvents, 0); + hasTsEvents.store(0, std::memory_order::memory_order_release); std::lock_guard lk(externalEventLock); // Move events from async queue into main queue @@ -579,7 +578,7 @@ void Advance() globalTimer += cyclesExecuted; currentMIPS->downcount = slicelength; - if (Common::AtomicLoadAcquire(hasTsEvents)) + if (hasTsEvents.load(std::memory_order_acquire)) MoveEvents(); ProcessFifoWaitEvents(); diff --git a/Core/Dialog/PSPOskDialog.cpp b/Core/Dialog/PSPOskDialog.cpp index 958e0cb240..a475d0ec36 100755 --- a/Core/Dialog/PSPOskDialog.cpp +++ b/Core/Dialog/PSPOskDialog.cpp @@ -766,7 +766,6 @@ void PSPOskDialog::RenderKeyboard() float previewLeftSide = (480.0f - (12.0f * drawLimit)) / 2.0f; float title = (480.0f - (0.5f * drawLimit)) / 2.0f; - PPGeDrawText(oskDesc.c_str(), title , 20, PPGE_ALIGN_CENTER, 0.5f, CalcFadedColor(0xFFFFFFFF)); std::u16string result; diff --git a/Core/HLE/__sceAudio.cpp b/Core/HLE/__sceAudio.cpp index 31573526ad..e462a94efd 100644 --- a/Core/HLE/__sceAudio.cpp +++ b/Core/HLE/__sceAudio.cpp @@ -21,7 +21,6 @@ #include "Common/CommonTypes.h" #include "Common/ChunkFile.h" #include "Common/FixedSizeQueue.h" -#include "Common/Atomics.h" #ifdef _M_SSE #include @@ -47,7 +46,6 @@ #include "Core/Util/AudioFormat.h" StereoResampler resampler; -AudioDebugStats g_AudioDebugStats; // Should be used to lock anything related to the outAudioQueue. // atomic locks are used on the lock. TODO: make this lock-free @@ -100,7 +98,7 @@ static void __AudioCPUMHzChange() { void __AudioInit() { - memset(&g_AudioDebugStats, 0, sizeof(g_AudioDebugStats)); + resampler.ResetStatCounters(); mixFrequency = 44100; srcFrequency = 0; @@ -459,9 +457,8 @@ int __AudioMix(short *outstereo, int numFrames, int sampleRate) { return resampler.Mix(outstereo, numFrames, false, sampleRate); } -const AudioDebugStats *__AudioGetDebugStats() { - resampler.GetAudioDebugStats(&g_AudioDebugStats); - return &g_AudioDebugStats; +void __AudioGetDebugStats(char *buf, size_t bufSize) { + resampler.GetAudioDebugStats(buf, bufSize); } void __PushExternalAudio(const s32 *audio, int numSamples) { diff --git a/Core/HLE/__sceAudio.h b/Core/HLE/__sceAudio.h index c836bd3f19..f6619ece7d 100644 --- a/Core/HLE/__sceAudio.h +++ b/Core/HLE/__sceAudio.h @@ -26,6 +26,7 @@ struct AudioDebugStats { int underrunCount; int overrunCount; int instantSampleRate; + int targetSampleRate; int lastPushSize; }; @@ -44,7 +45,7 @@ void __AudioWakeThreads(AudioChannel &chan, int result, int step); void __AudioWakeThreads(AudioChannel &chan, int result); int __AudioMix(short *outstereo, int numSamples, int sampleRate); -const AudioDebugStats *__AudioGetDebugStats(); +void __AudioGetDebugStats(char *buf, size_t bufSize); void __PushExternalAudio(const s32 *audio, int numSamples); // Should not be used in-game, only at the menu! // Audio Dumping stuff diff --git a/Core/HLE/sceMp4.cpp b/Core/HLE/sceMp4.cpp index 99dd7ac4c7..ca2c3d3e11 100644 --- a/Core/HLE/sceMp4.cpp +++ b/Core/HLE/sceMp4.cpp @@ -76,16 +76,12 @@ static u32 sceMp4GetMovieInfo(u32 mp4, u32 unknown2) return 0; } -static u32 sceMp4TrackSampleBufAvailableSize(u32 mp4, u32 unknown2) -{ - ERROR_LOG(ME, "UNIMPL sceMp4TrackSampleBufAvailableSize(mp4 %i, unknown2 %08x)", mp4, unknown2); - return 0; +static u32 sceMp4TrackSampleBufAvailableSize(u32 mp4, u32 trackAddr, u32 writableSamplesAddr, u32 writableBytesAddr) { + return hleLogError(ME, 0, "unimplemented"); } -static u32 sceMp4Delete() -{ - ERROR_LOG(ME, "UNIMPL sceMp4Delete()"); - return 0; +static u32 sceMp4Delete(u32 mp4) { + return hleLogError(ME, 0, "unimplemented"); } static u32 sceMp4AacDecodeInitResource(int unknown) @@ -402,7 +398,7 @@ const HLEFunction sceMp4[] = {0X68651CBC, &WrapU_V, "sceMp4Init", 'x', "" }, {0X9042B257, &WrapU_V, "sceMp4Finish", 'x', "" }, {0XB1221EE7, &WrapU_UUUU, "sceMp4Create", 'x', "xxxx" }, - {0X538C2057, &WrapU_V, "sceMp4Delete", 'x', "" }, + {0X538C2057, &WrapU_U, "sceMp4Delete", 'x', "x" }, {0X113E9E7B, &WrapU_V, "sceMp4GetNumberOfMetaData", 'x', "" }, {0X7443AF1D, &WrapU_UU, "sceMp4GetMovieInfo", 'x', "xx" }, {0X5EB65F26, &WrapU_V, "sceMp4GetNumberOfSpecificTrack", 'x', "" }, @@ -418,7 +414,7 @@ const HLEFunction sceMp4[] = {0XF7C51EC1, &WrapU_V, "sceMp4GetSampleInfo", 'x', "" }, {0X74A1CA3E, &WrapU_V, "sceMp4SearchSyncSampleNum", 'x', "" }, {0XD8250B75, nullptr, "sceMp4PutSampleNum", '?', "" }, - {0X8754ECB8, &WrapU_UU, "sceMp4TrackSampleBufAvailableSize", 'x', "xx" }, + {0X8754ECB8, &WrapU_UUUU, "sceMp4TrackSampleBufAvailableSize", 'x', "xppp" }, {0X31BCD7E0, nullptr, "sceMp4TrackSampleBufPut", '?', "" }, {0X5601A6F0, &WrapU_UUUU, "sceMp4GetAacAu", 'x', "xxxx" }, {0X7663CB5C, &WrapU_UUUUU, "sceMp4AacDecode", 'x', "xxxxx" }, diff --git a/Core/HLE/scePsmf.cpp b/Core/HLE/scePsmf.cpp index 0154e9f085..7d3eee9680 100644 --- a/Core/HLE/scePsmf.cpp +++ b/Core/HLE/scePsmf.cpp @@ -1441,8 +1441,6 @@ static int scePsmfPlayerStart(u32 psmfPlayer, u32 psmfPlayerData, int initPts) return ERROR_PSMFPLAYER_INVALID_PARAM; } - WARN_LOG(ME, "scePsmfPlayerStart(%08x, %08x, %d)", psmfPlayer, psmfPlayerData, initPts); - psmfplayer->AbortFinish(); psmfplayer->mediaengine->setVideoStream(playerData->videoStreamNum); psmfplayer->videoCodec = playerData->videoCodec; @@ -1455,6 +1453,8 @@ static int scePsmfPlayerStart(u32 psmfPlayer, u32 psmfPlayerData, int initPts) psmfplayer->playMode = playerData->playMode; psmfplayer->playSpeed = playerData->playSpeed; + WARN_LOG(ME, "scePsmfPlayerStart(%08x, %08x, %d,(mode %d, speed %d)", psmfPlayer, psmfPlayerData, initPts, playerData->playMode, playerData->playSpeed); + // Does not alter current pts, it just catches up when Update()/etc. get there. int delayUs = psmfplayer->status == PSMF_PLAYER_STATUS_PLAYING ? 3000 : 0; diff --git a/Core/HW/StereoResampler.cpp b/Core/HW/StereoResampler.cpp index 3894e6d771..a6e06fafcf 100644 --- a/Core/HW/StereoResampler.cpp +++ b/Core/HW/StereoResampler.cpp @@ -19,23 +19,27 @@ // 16 bit Stereo -#define MAX_SAMPLES_DEFAULT (4096) // 2*64ms - had to double it for nVidia Shield which has huge buffers -#define MAX_SAMPLES_EXTRA (8192) +// These must be powers of 2. +#define MAX_BUFSIZE_DEFAULT (4096) // 2*64ms - had to double it for nVidia Shield which has huge buffers +#define MAX_BUFSIZE_EXTRA (8192) -#define LOW_WATERMARK_DEFAULT 1680 // 40 ms -#define LOW_WATERMARK_EXTRA 3360 // 80 ms +#define TARGET_BUFSIZE_MARGIN 512 -#define MAX_FREQ_SHIFT 200 // per 32000 Hz +#define TARGET_BUFSIZE_DEFAULT 1680 // 40 ms +#define TARGET_BUFSIZE_EXTRA 3360 // 80 ms + +#define MAX_FREQ_SHIFT 600.0f // how far off can we be from 44100 Hz #define CONTROL_FACTOR 0.2f // in freq_shift per fifo size offset -#define CONTROL_AVG 32 +#define CONTROL_AVG 32.0f #include +#include #include "base/logging.h" +#include "base/timeutil.h" #include "base/NativeApp.h" #include "Common/ChunkFile.h" #include "Common/MathUtil.h" -#include "Common/Atomics.h" #include "Core/Config.h" #include "Core/ConfigValues.h" #include "Core/HW/StereoResampler.h" @@ -55,19 +59,10 @@ #endif StereoResampler::StereoResampler() - : m_bufsize(MAX_SAMPLES_DEFAULT) - , m_lowwatermark(LOW_WATERMARK_DEFAULT) - , m_input_sample_rate(44100) - , m_indexW(0) - , m_indexR(0) - , m_numLeftI(0.0f) - , m_frac(0) - , underrunCount_(0) - , overrunCount_(0) - , sample_rate_(0.0f) - , lastBufSize_(0) { + : m_maxBufsize(MAX_BUFSIZE_DEFAULT) + , m_targetBufsize(TARGET_BUFSIZE_DEFAULT) { // Need to have space for the worst case in case it changes. - m_buffer = new int16_t[MAX_SAMPLES_EXTRA * 2](); + m_buffer = new int16_t[MAX_BUFSIZE_EXTRA * 2](); // Some Android devices are v-synced to non-60Hz framerates. We simply timestretch audio to fit. // TODO: should only do this if auto frameskip is off? @@ -75,7 +70,9 @@ StereoResampler::StereoResampler() // If framerate is "close"... if (refresh != 60.0f && refresh > 50.0f && refresh < 70.0f) { - SetInputSampleRate((int)(44100 * (refresh / 60.0f))); + int input_sample_rate = (int)(44100 * (refresh / 60.0f)); + ILOG("StereoResampler: Adjusting target sample rate to %dHz", input_sample_rate); + m_input_sample_rate = input_sample_rate; } UpdateBufferSize(); @@ -88,11 +85,18 @@ StereoResampler::~StereoResampler() { void StereoResampler::UpdateBufferSize() { if (g_Config.bExtraAudioBuffering) { - m_bufsize = MAX_SAMPLES_EXTRA; - m_lowwatermark = LOW_WATERMARK_EXTRA; + m_maxBufsize = MAX_BUFSIZE_EXTRA; + m_targetBufsize = TARGET_BUFSIZE_EXTRA; } else { - m_bufsize = MAX_SAMPLES_DEFAULT; - m_lowwatermark = LOW_WATERMARK_DEFAULT; + m_maxBufsize = MAX_BUFSIZE_DEFAULT; + m_targetBufsize = TARGET_BUFSIZE_DEFAULT; + + int systemBufsize = System_GetPropertyInt(SYSPROP_AUDIO_FRAMES_PER_BUFFER); + if (systemBufsize > 0 && m_targetBufsize < systemBufsize + TARGET_BUFSIZE_MARGIN) { + m_targetBufsize = std::min(4096, systemBufsize + TARGET_BUFSIZE_MARGIN); + if (m_targetBufsize * 2 > MAX_BUFSIZE_DEFAULT) + m_maxBufsize = MAX_BUFSIZE_EXTRA; + } } } @@ -154,70 +158,80 @@ inline void ClampBufferToS16WithVolume(s16 *out, const s32 *in, size_t size) { } void StereoResampler::Clear() { - memset(m_buffer, 0, m_bufsize * 2 * sizeof(int16_t)); + memset(m_buffer, 0, m_maxBufsize * 2 * sizeof(int16_t)); } -// Executed from sound stream thread +// Executed from sound stream thread, pulling sound out of the buffer. unsigned int StereoResampler::Mix(short* samples, unsigned int numSamples, bool consider_framelimit, int sample_rate) { if (!samples) return 0; - unsigned int currentSample = 0; + unsigned int currentSample; // Cache access in non-volatile variable // This is the only function changing the read value, so it's safe to // cache it locally although it's written here. // The writing pointer will be modified outside, but it will only increase, - // so we will just ignore new written data while interpolating. + // so we will just ignore new written data while interpolating (until it wraps...). // Without this cache, the compiler wouldn't be allowed to optimize the // interpolation loop. - u32 indexR = Common::AtomicLoad(m_indexR); - u32 indexW = Common::AtomicLoad(m_indexW); + u32 indexR = m_indexR.load(); + u32 indexW = m_indexW.load(); - const int INDEX_MASK = (m_bufsize * 2 - 1); + const int INDEX_MASK = (m_maxBufsize * 2 - 1); + lastBufSize_ = (indexR - m_indexW) & INDEX_MASK; - // We force on the audio resampler if the output sample rate doesn't match the input. - if (!g_Config.bAudioResampler && sample_rate == (int)m_input_sample_rate) { - for (; currentSample < numSamples * 2 && ((indexW - indexR) & INDEX_MASK) > 2; currentSample += 2) { - s16 l1 = m_buffer[indexR & INDEX_MASK]; //current - s16 r1 = m_buffer[(indexR + 1) & INDEX_MASK]; //current - samples[currentSample] = l1; - samples[currentSample + 1] = r1; - indexR += 2; - } - sample_rate_ = (float)sample_rate; - } else { - // Drift prevention mechanism - float numLeft = (float)(((indexW - indexR) & INDEX_MASK) / 2); - m_numLeftI = (numLeft + m_numLeftI*(CONTROL_AVG - 1)) / CONTROL_AVG; - float offset = (m_numLeftI - m_lowwatermark) * CONTROL_FACTOR; - if (offset > MAX_FREQ_SHIFT) offset = MAX_FREQ_SHIFT; - if (offset < -MAX_FREQ_SHIFT) offset = -MAX_FREQ_SHIFT; + // Drift prevention mechanism. + float numLeft = (float)(((indexW - indexR) & INDEX_MASK) / 2); + // If we had to discard samples the last frame due to underrun, + // apply an adjustment here. Otherwise we'll overestimate how many + // samples we need. + numLeft -= droppedSamples_; + droppedSamples_ = 0; - sample_rate_ = (float)(m_input_sample_rate + offset); - const u32 ratio = (u32)(65536.0 * sample_rate_ / (double)sample_rate); + // m_numLeftI here becomes a lowpass filtered version of numLeft. + m_numLeftI = (numLeft + m_numLeftI * (CONTROL_AVG - 1.0f)) / CONTROL_AVG; - // TODO: consider a higher-quality resampling algorithm. - // TODO: Add a fast path for 1:1. - for (; currentSample < numSamples * 2 && ((indexW - indexR) & INDEX_MASK) > 2; currentSample += 2) { - u32 indexR2 = indexR + 2; //next sample - s16 l1 = m_buffer[indexR & INDEX_MASK]; //current - s16 r1 = m_buffer[(indexR + 1) & INDEX_MASK]; //current - s16 l2 = m_buffer[indexR2 & INDEX_MASK]; //next - s16 r2 = m_buffer[(indexR2 + 1) & INDEX_MASK]; //next - int sampleL = ((l1 << 16) + (l2 - l1) * (u16)m_frac) >> 16; - int sampleR = ((r1 << 16) + (r2 - r1) * (u16)m_frac) >> 16; - samples[currentSample] = sampleL; - samples[currentSample + 1] = sampleR; - m_frac += ratio; - indexR += 2 * (u16)(m_frac >> 16); - m_frac &= 0xffff; + // Here we try to keep the buffer size around m_lowwatermark (which is + // really now more like desired_buffer_size) by adjusting the speed. + // Note that the speed of adjustment here does not take the buffer size into + // account. Since this is called once per "output frame", the frame size + // will affect how fast this algorithm reacts, which can't be a good thing. + float offset = (m_numLeftI - (float)m_targetBufsize) * CONTROL_FACTOR; + if (offset > MAX_FREQ_SHIFT) offset = MAX_FREQ_SHIFT; + if (offset < -MAX_FREQ_SHIFT) offset = -MAX_FREQ_SHIFT; + + output_sample_rate_ = (float)(m_input_sample_rate + offset); + const u32 ratio = (u32)(65536.0 * output_sample_rate_ / (double)sample_rate); + ratio_ = ratio; + // TODO: consider a higher-quality resampling algorithm. + // TODO: Add a fast path for 1:1. + u32 frac = m_frac; + for (currentSample = 0; currentSample < numSamples * 2; currentSample += 2) { + if (((indexW - indexR) & INDEX_MASK) <= 2) { + // Ran out! + // int missing = numSamples * 2 - currentSample; + // ILOG("Resampler underrun: %d (numSamples: %d, currentSample: %d)", missing, numSamples, currentSample / 2); + underrunCount_++; + break; } + u32 indexR2 = indexR + 2; //next sample + s16 l1 = m_buffer[indexR & INDEX_MASK]; //current + s16 r1 = m_buffer[(indexR + 1) & INDEX_MASK]; //current + s16 l2 = m_buffer[indexR2 & INDEX_MASK]; //next + s16 r2 = m_buffer[(indexR2 + 1) & INDEX_MASK]; //next + int sampleL = ((l1 << 16) + (l2 - l1) * (u16)frac) >> 16; + int sampleR = ((r1 << 16) + (r2 - r1) * (u16)frac) >> 16; + samples[currentSample] = sampleL; + samples[currentSample + 1] = sampleR; + frac += ratio; + indexR += 2 * (frac >> 16); + frac &= 0xffff; } + m_frac = frac; - int realSamples = currentSample; - if (currentSample < numSamples * 2) - underrunCount_++; + // Let's not count the underrun padding here. + outputSampleCount_ += currentSample / 2; // Padding with the last value to reduce clicking short s[2]; @@ -229,64 +243,97 @@ unsigned int StereoResampler::Mix(short* samples, unsigned int numSamples, bool } // Flush cached variable - Common::AtomicStore(m_indexR, indexR); + m_indexR.store(indexR); - //if (realSamples != numSamples * 2) { - // ILOG("Underrun! %i / %i", realSamples / 2, numSamples); - //} - lastBufSize_ = (m_indexW - m_indexR) & INDEX_MASK; - - return realSamples / 2; + // TODO: What should we actually return here? + return currentSample / 2; } -void StereoResampler::PushSamples(const s32 *samples, unsigned int num_samples) { +// Executes on the emulator thread, pushing sound into the buffer. +void StereoResampler::PushSamples(const s32 *samples, unsigned int numSamples) { + inputSampleCount_ += numSamples; + UpdateBufferSize(); - const int INDEX_MASK = (m_bufsize * 2 - 1); + const int INDEX_MASK = (m_maxBufsize * 2 - 1); // Cache access in non-volatile variable // indexR isn't allowed to cache in the audio throttling loop as it // needs to get updates to not deadlock. - u32 indexW = Common::AtomicLoad(m_indexW); + u32 indexW = m_indexW.load(); - u32 cap = m_bufsize * 2; - // If unthottling, no need to fill up the entire buffer, just screws up timing after releasing unthrottle. - if (PSP_CoreParameter().unthrottle) - cap = m_lowwatermark * 2; + u32 cap = m_maxBufsize * 2; + // If unthrottling, no need to fill up the entire buffer, just screws up timing after releasing unthrottle. + if (PSP_CoreParameter().unthrottle) { + cap = m_targetBufsize * 2; + } // Check if we have enough free space // indexW == m_indexR results in empty buffer, so indexR must always be smaller than indexW - if (num_samples * 2 + ((indexW - Common::AtomicLoad(m_indexR)) & INDEX_MASK) >= cap) { - if (!PSP_CoreParameter().unthrottle) + if (numSamples * 2 + ((indexW - m_indexR.load()) & INDEX_MASK) >= cap) { + if (!PSP_CoreParameter().unthrottle) { overrunCount_++; + } // TODO: "Timestretch" by doing a windowed overlap with existing buffer content? return; } - int over_bytes = num_samples * 4 - (m_bufsize * 2 - (indexW & INDEX_MASK)) * sizeof(short); + int over_bytes = numSamples * 4 - (m_maxBufsize * 2 - (indexW & INDEX_MASK)) * sizeof(short); if (over_bytes > 0) { - ClampBufferToS16WithVolume(&m_buffer[indexW & INDEX_MASK], samples, (num_samples * 4 - over_bytes) / 2); - ClampBufferToS16WithVolume(&m_buffer[0], samples + (num_samples * 4 - over_bytes) / sizeof(short), over_bytes / 2); + ClampBufferToS16WithVolume(&m_buffer[indexW & INDEX_MASK], samples, (numSamples * 4 - over_bytes) / 2); + ClampBufferToS16WithVolume(&m_buffer[0], samples + (numSamples * 4 - over_bytes) / sizeof(short), over_bytes / 2); } else { - ClampBufferToS16WithVolume(&m_buffer[indexW & INDEX_MASK], samples, num_samples * 2); + ClampBufferToS16WithVolume(&m_buffer[indexW & INDEX_MASK], samples, numSamples * 2); } - Common::AtomicAdd(m_indexW, num_samples * 2); - lastPushSize_ = num_samples; + m_indexW += numSamples * 2; + lastPushSize_ = numSamples; } -void StereoResampler::GetAudioDebugStats(AudioDebugStats *stats) { - stats->buffered = lastBufSize_; - stats->underrunCount += underrunCount_; +void StereoResampler::GetAudioDebugStats(char *buf, size_t bufSize) { + double elapsed = real_time_now() - startTime_; + + double effective_input_sample_rate = (double)inputSampleCount_ / elapsed; + double effective_output_sample_rate = (double)outputSampleCount_ / elapsed; + snprintf(buf, bufSize, + "Audio buffer: %d/%d (target: %d)\n" + "Filtered: %0.2f\n" + "Underruns: %d\n" + "Overruns: %d\n" + "Sample rate: %d (input: %d)\n" + "Effective input sample rate: %0.2f\n" + "Effective output sample rate: %0.2f\n" + "Push size: %d\n" + "Ratio: %0.6f\n", + lastBufSize_, + m_maxBufsize, + m_targetBufsize, + m_numLeftI, + underrunCountTotal_, + overrunCountTotal_, + (int)output_sample_rate_, + m_input_sample_rate, + effective_input_sample_rate, + effective_output_sample_rate, + lastPushSize_, + (float)ratio_ / 65536.0f); + underrunCountTotal_ += underrunCount_; + overrunCountTotal_ += overrunCount_; underrunCount_ = 0; - stats->overrunCount += overrunCount_; overrunCount_ = 0; - stats->watermark = m_lowwatermark; - stats->bufsize = m_bufsize * 2; - stats->instantSampleRate = (int)sample_rate_; - stats->lastPushSize = lastPushSize_; + + // Use this to remove the bias from the startup. + // if (elapsed > 3.0) { + //ResetStatCounters(); + // } } -void StereoResampler::SetInputSampleRate(unsigned int rate) { - m_input_sample_rate = rate; +void StereoResampler::ResetStatCounters() { + underrunCount_ = 0; + overrunCount_ = 0; + underrunCountTotal_ = 0; + overrunCountTotal_ = 0; + inputSampleCount_ = 0; + outputSampleCount_ = 0; + startTime_ = real_time_now(); } void StereoResampler::DoState(PointerWrap &p) { diff --git a/Core/HW/StereoResampler.h b/Core/HW/StereoResampler.h index 57dbe37773..451b598d66 100644 --- a/Core/HW/StereoResampler.h +++ b/Core/HW/StereoResampler.h @@ -19,7 +19,8 @@ #pragma once -#include +#include +#include #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" @@ -42,23 +43,36 @@ public: void DoState(PointerWrap &p); - void GetAudioDebugStats(AudioDebugStats *stats); + void GetAudioDebugStats(char *buf, size_t bufSize); + void ResetStatCounters(); -protected: +private: void UpdateBufferSize(); - void SetInputSampleRate(unsigned int rate); - int m_bufsize; - int m_lowwatermark; - unsigned int m_input_sample_rate; + int m_maxBufsize; + int m_targetBufsize; + + unsigned int m_input_sample_rate = 44100; int16_t *m_buffer; - volatile u32 m_indexW; - volatile u32 m_indexR; - float m_numLeftI; - u32 m_frac; - int underrunCount_; - int overrunCount_; - float sample_rate_; - int lastBufSize_; - int lastPushSize_; + std::atomic m_indexW; + std::atomic m_indexR; + float m_numLeftI = 0.0f; + + u32 m_frac = 0; + float output_sample_rate_ = 0.0; + int lastBufSize_ = 0; + int lastPushSize_ = 0; + u32 ratio_ = 0; + + int underrunCount_ = 0; + int overrunCount_ = 0; + int underrunCountTotal_ = 0; + int overrunCountTotal_ = 0; + + int droppedSamples_ = 0; + + int64_t inputSampleCount_ = 0; + int64_t outputSampleCount_ = 0; + + double startTime_ = 0.0; }; diff --git a/Core/MIPS/ARM/ArmJit.cpp b/Core/MIPS/ARM/ArmJit.cpp index 5820509c78..f20e5e2600 100644 --- a/Core/MIPS/ARM/ArmJit.cpp +++ b/Core/MIPS/ARM/ArmJit.cpp @@ -304,17 +304,17 @@ const u8 *ArmJit::DoJit(u32 em_address, JitBlock *b) JumpTarget backJump = GetCodePtr(); gpr.SetRegImm(R0, js.blockStart); B((const void *)outerLoopPCInR0); - b->checkedEntry = (u8 *)GetCodePtr(); + b->checkedEntry = GetCodePtr(); SetCC(CC_LT); B(backJump); SetCC(CC_AL); } else if (jo.useForwardJump) { - b->checkedEntry = (u8 *)GetCodePtr(); + b->checkedEntry = GetCodePtr(); SetCC(CC_LT); bail = B(); SetCC(CC_AL); } else { - b->checkedEntry = (u8 *)GetCodePtr(); + b->checkedEntry = GetCodePtr(); SetCC(CC_LT); gpr.SetRegImm(R0, js.blockStart); B((const void *)outerLoopPCInR0); diff --git a/Core/MIPS/ARM64/Arm64Jit.cpp b/Core/MIPS/ARM64/Arm64Jit.cpp index 8501379a0b..330c6b4761 100644 --- a/Core/MIPS/ARM64/Arm64Jit.cpp +++ b/Core/MIPS/ARM64/Arm64Jit.cpp @@ -300,13 +300,13 @@ const u8 *Arm64Jit::DoJit(u32 em_address, JitBlock *b) { const u8 *backJump = GetCodePtr(); MOVI2R(SCRATCH1, js.blockStart); B((const void *)outerLoopPCInSCRATCH1); - b->checkedEntry = (u8 *)GetCodePtr(); + b->checkedEntry = GetCodePtr(); B(CC_LT, backJump); } else if (jo.useForwardJump) { - b->checkedEntry = (u8 *)GetCodePtr(); + b->checkedEntry = GetCodePtr(); bail = B(CC_LT); } else if (jo.enableBlocklink) { - b->checkedEntry = (u8 *)GetCodePtr(); + b->checkedEntry = GetCodePtr(); MOVI2R(SCRATCH1, js.blockStart); FixupBranch skip = B(CC_GE); B((const void *)outerLoopPCInSCRATCH1); @@ -442,7 +442,7 @@ void Arm64Jit::LinkBlock(u8 *exitPoint, const u8 *checkedEntry) { if (PlatformIsWXExclusive()) { ProtectMemoryPages(exitPoint, 32, MEM_PROT_READ | MEM_PROT_WRITE); } - ARM64XEmitter emit(exitPoint); + ARM64XEmitter emit(GetCodePtrFromWritablePtr(exitPoint), exitPoint); emit.B(checkedEntry); // TODO: Write stuff after, convering up the now-unused instructions. emit.FlushIcache(); @@ -459,7 +459,7 @@ void Arm64Jit::UnlinkBlock(u8 *checkedEntry, u32 originalAddress) { ProtectMemoryPages(checkedEntry, 16, MEM_PROT_READ | MEM_PROT_WRITE); } - ARM64XEmitter emit(checkedEntry); + ARM64XEmitter emit(GetCodePtrFromWritablePtr(checkedEntry), checkedEntry); emit.MOVI2R(SCRATCH1, originalAddress); emit.STR(INDEX_UNSIGNED, SCRATCH1, CTXREG, offsetof(MIPSState, pc)); emit.B(MIPSComp::jit->GetDispatcher()); diff --git a/Core/MIPS/JitCommon/JitBlockCache.cpp b/Core/MIPS/JitCommon/JitBlockCache.cpp index 6958fc894a..fa9ca1edd8 100644 --- a/Core/MIPS/JitCommon/JitBlockCache.cpp +++ b/Core/MIPS/JitCommon/JitBlockCache.cpp @@ -186,7 +186,7 @@ void JitBlockCache::ProxyBlock(u32 rootAddress, u32 startAddress, u32 size, cons // Make binary searches and stuff work ok b.normalEntry = codePtr; - b.checkedEntry = (u8 *)codePtr; // Ugh, casting away const.. + b.checkedEntry = codePtr; proxyBlockMap_.insert(std::make_pair(startAddress, num_blocks_)); AddBlockMap(num_blocks_); @@ -541,7 +541,8 @@ void JitBlockCache::DestroyBlock(int block_num, DestroyType type) { if (b->checkedEntry) { // We can skip this if we're clearing anyway, which cuts down on protect back and forth on WX exclusive. if (type != DestroyType::CLEAR) { - MIPSComp::jit->UnlinkBlock(b->checkedEntry, b->originalAddress); + u8 *writableEntry = codeBlock_->GetWritablePtrFromCodePtr(b->checkedEntry); + MIPSComp::jit->UnlinkBlock(writableEntry, b->originalAddress); } } else { ERROR_LOG(JIT, "Unlinking block with no entry: %08x (%d)", b->originalAddress, block_num); diff --git a/Core/MIPS/JitCommon/JitBlockCache.h b/Core/MIPS/JitCommon/JitBlockCache.h index ce609b8ada..3c51a67f66 100644 --- a/Core/MIPS/JitCommon/JitBlockCache.h +++ b/Core/MIPS/JitCommon/JitBlockCache.h @@ -59,7 +59,7 @@ enum class DestroyType { struct JitBlock { bool ContainsAddress(u32 em_address); - u8 *checkedEntry; // not const, may need to write through this to unlink + const u8 *checkedEntry; // const, we have to translate to writable. const u8 *normalEntry; u8 *exitPtrs[MAX_JIT_BLOCK_EXITS]; // to be able to rewrite the exit jump diff --git a/Core/MIPS/x86/Jit.cpp b/Core/MIPS/x86/Jit.cpp index 9d3064dddd..a8050ac0ac 100644 --- a/Core/MIPS/x86/Jit.cpp +++ b/Core/MIPS/x86/Jit.cpp @@ -335,7 +335,7 @@ const u8 *Jit::DoJit(u32 em_address, JitBlock *b) { js.PrefixStart(); // We add a check before the block, used when entering from a linked block. - b->checkedEntry = (u8 *)GetCodePtr(); + b->checkedEntry = GetCodePtr(); // Downcount flag check. The last block decremented downcounter, and the flag should still be available. FixupBranch skip = J_CC(CC_NS); MOV(32, MIPSSTATE_VAR(pc), Imm32(js.blockStart)); diff --git a/Core/MemMapFunctions.cpp b/Core/MemMapFunctions.cpp index a463a0e0ae..ac543e36bd 100644 --- a/Core/MemMapFunctions.cpp +++ b/Core/MemMapFunctions.cpp @@ -16,7 +16,6 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include "Common/Common.h" -#include "Common/Atomics.h" #include "Core/Core.h" #include "Core/MemMap.h" diff --git a/Core/SaveState.cpp b/Core/SaveState.cpp index 7ab068abc5..92fad1e07a 100644 --- a/Core/SaveState.cpp +++ b/Core/SaveState.cpp @@ -71,14 +71,16 @@ namespace SaveState struct Operation { - Operation(OperationType t, const std::string &f, Callback cb, void *cbUserData_) - : type(t), filename(f), callback(cb), cbUserData(cbUserData_) + // The slot number is for visual purposes only. Set to -1 for operations where we don't display a message for example. + Operation(OperationType t, const std::string &f, int slot_, Callback cb, void *cbUserData_) + : type(t), filename(f), callback(cb), slot(slot_), cbUserData(cbUserData_) { } OperationType type; std::string filename; Callback callback; + int slot; void *cbUserData; }; @@ -317,29 +319,29 @@ namespace SaveState Core_UpdateSingleStep(); } - void Load(const std::string &filename, Callback callback, void *cbUserData) + void Load(const std::string &filename, int slot, Callback callback, void *cbUserData) { - Enqueue(Operation(SAVESTATE_LOAD, filename, callback, cbUserData)); + Enqueue(Operation(SAVESTATE_LOAD, filename, slot, callback, cbUserData)); } - void Save(const std::string &filename, Callback callback, void *cbUserData) + void Save(const std::string &filename, int slot, Callback callback, void *cbUserData) { - Enqueue(Operation(SAVESTATE_SAVE, filename, callback, cbUserData)); + Enqueue(Operation(SAVESTATE_SAVE, filename, slot, callback, cbUserData)); } void Verify(Callback callback, void *cbUserData) { - Enqueue(Operation(SAVESTATE_VERIFY, std::string(""), callback, cbUserData)); + Enqueue(Operation(SAVESTATE_VERIFY, std::string(""), -1, callback, cbUserData)); } void Rewind(Callback callback, void *cbUserData) { - Enqueue(Operation(SAVESTATE_REWIND, std::string(""), callback, cbUserData)); + Enqueue(Operation(SAVESTATE_REWIND, std::string(""), -1, callback, cbUserData)); } void SaveScreenshot(const std::string &filename, Callback callback, void *cbUserData) { - Enqueue(Operation(SAVESTATE_SAVE_SCREENSHOT, filename, callback, cbUserData)); + Enqueue(Operation(SAVESTATE_SAVE_SCREENSHOT, filename, -1, callback, cbUserData)); } bool CanRewind() @@ -436,7 +438,7 @@ namespace SaveState { std::string fn = GenerateSaveSlotFilename(gameFilename, slot, STATE_EXTENSION); if (!fn.empty()) { - Load(fn, callback, cbUserData); + Load(fn, slot, callback, cbUserData); } else { auto sy = GetI18NCategory("System"); if (callback) @@ -493,7 +495,7 @@ namespace SaveState RenameIfExists(shot, shotUndo); } SaveScreenshot(shot, Callback(), 0); - Save(fn + ".tmp", renameCallback, cbUserData); + Save(fn + ".tmp", slot, renameCallback, cbUserData); } else { auto sy = GetI18NCategory("System"); if (callback) @@ -738,6 +740,8 @@ namespace SaveState if (strlen(i18nSaveFailure) == 0) i18nSaveFailure = sc->T("Failed to save state"); + std::string slot_prefix = op.slot >= 0 ? StringFromFormat("(%d) ", op.slot + 1) : ""; + switch (op.type) { case SAVESTATE_LOAD: @@ -745,7 +749,7 @@ namespace SaveState // Use the state's latest version as a guess for saveStateInitialGitVersion. result = CChunkFileReader::Load(op.filename, &saveStateInitialGitVersion, state, &reason); if (result == CChunkFileReader::ERROR_NONE) { - callbackMessage = sc->T("Loaded State"); + callbackMessage = slot_prefix + sc->T("Loaded State"); callbackResult = Status::SUCCESS; hasLoadedState = true; @@ -754,11 +758,11 @@ namespace SaveState // Using save states instead of saves simulates many hour play sessions. // Sometimes this exposes game bugs that were rarely seen on real devices, // because few people played on a real PSP for 10 hours straight. - callbackMessage = sc->T("Loaded. Save in game, restart, and load for less bugs."); + callbackMessage = slot_prefix + sc->T("Loaded. Save in game, restart, and load for less bugs."); callbackResult = Status::WARNING; } else if (!g_Config.bHideStateWarnings && IsOldVersion()) { // Save states also preserve bugs from old PPSSPP versions, so warn. - callbackMessage = sc->T("Loaded. Save in game, restart, and load for less bugs."); + callbackMessage = slot_prefix + sc->T("Loaded. Save in game, restart, and load for less bugs."); callbackResult = Status::WARNING; } @@ -795,7 +799,7 @@ namespace SaveState } result = CChunkFileReader::Save(op.filename, title, PPSSPP_GIT_VERSION, state); if (result == CChunkFileReader::ERROR_NONE) { - callbackMessage = sc->T("Saved State"); + callbackMessage = slot_prefix + sc->T("Saved State"); callbackResult = Status::SUCCESS; #ifndef MOBILE_DEVICE if (g_Config.bSaveLoadResetsAVdumping) { diff --git a/Core/SaveState.h b/Core/SaveState.h index e190e6e65d..8a205ee579 100644 --- a/Core/SaveState.h +++ b/Core/SaveState.h @@ -62,11 +62,11 @@ namespace SaveState // Load the specified file into the current state (async.) // Warning: callback will be called on a different thread. - void Load(const std::string &filename, Callback callback = Callback(), void *cbUserData = 0); + void Load(const std::string &filename, int slot, Callback callback = Callback(), void *cbUserData = 0); // Save the current state to the specified file (async.) // Warning: callback will be called on a different thread. - void Save(const std::string &filename, Callback callback = Callback(), void *cbUserData = 0); + void Save(const std::string &filename, int slot, Callback callback = Callback(), void *cbUserData = 0); CChunkFileReader::Error SaveToRam(std::vector &state); CChunkFileReader::Error LoadFromRam(std::vector &state); diff --git a/GPU/Common/FramebufferCommon.cpp b/GPU/Common/FramebufferCommon.cpp index c2aca9a040..87e400e087 100644 --- a/GPU/Common/FramebufferCommon.cpp +++ b/GPU/Common/FramebufferCommon.cpp @@ -1353,11 +1353,10 @@ VirtualFramebuffer *FramebufferManagerCommon::CreateRAMFramebuffer(uint32_t fbAd VirtualFramebuffer *FramebufferManagerCommon::FindDownloadTempBuffer(VirtualFramebuffer *vfb) { // For now we'll keep these on the same struct as the ones that can get displayed // (and blatantly copy work already done above while at it). - VirtualFramebuffer *nvfb = 0; + VirtualFramebuffer *nvfb = nullptr; // We maintain a separate vector of framebuffer objects for blitting. - for (size_t i = 0; i < bvfbs_.size(); ++i) { - VirtualFramebuffer *v = bvfbs_[i]; + for (VirtualFramebuffer *v : bvfbs_) { if (v->fb_address == vfb->fb_address && v->format == vfb->format) { if (v->bufferWidth == vfb->bufferWidth && v->bufferHeight == vfb->bufferHeight) { nvfb = v; @@ -1407,6 +1406,34 @@ VirtualFramebuffer *FramebufferManagerCommon::FindDownloadTempBuffer(VirtualFram return nvfb; } +bool FramebufferManagerCommon::CreateDownloadTempBuffer(VirtualFramebuffer *nvfb) { + // When updating VRAM, it need to be exact format. + if (!gstate_c.Supports(GPU_PREFER_CPU_DOWNLOAD)) { + switch (nvfb->format) { + case GE_FORMAT_4444: + nvfb->colorDepth = Draw::FBO_4444; + break; + case GE_FORMAT_5551: + nvfb->colorDepth = Draw::FBO_5551; + break; + case GE_FORMAT_565: + nvfb->colorDepth = Draw::FBO_565; + break; + case GE_FORMAT_8888: + default: + nvfb->colorDepth = Draw::FBO_8888; + break; + } + } + + nvfb->fbo = draw_->CreateFramebuffer({ nvfb->bufferWidth, nvfb->bufferHeight, 1, 1, false, (Draw::FBColorDepth)nvfb->colorDepth }); + if (!nvfb->fbo) { + ERROR_LOG(FRAMEBUF, "Error creating GL FBO! %i x %i", nvfb->renderWidth, nvfb->renderHeight); + return false; + } + return true; +} + void FramebufferManagerCommon::ApplyClearToMemory(int x1, int y1, int x2, int y2, u32 clearColor) { if (currentRenderVfb_) { if ((currentRenderVfb_->usageFlags & FB_USAGE_DOWNLOAD_CLEAR) != 0) { @@ -1971,8 +1998,10 @@ void FramebufferManagerCommon::ReadFramebufferToMemory(VirtualFramebuffer *vfb, PackFramebufferSync_(vfb, x, y, w, h); } else { VirtualFramebuffer *nvfb = FindDownloadTempBuffer(vfb); - BlitFramebuffer(nvfb, x, y, vfb, x, y, w, h, 0); - PackFramebufferSync_(nvfb, x, y, w, h); + if (nvfb) { + BlitFramebuffer(nvfb, x, y, vfb, x, y, w, h, 0); + PackFramebufferSync_(nvfb, x, y, w, h); + } } textureCache_->ForgetLastTexture(); @@ -2016,9 +2045,10 @@ void FramebufferManagerCommon::DownloadFramebufferForClut(u32 fb_address, u32 lo // We'll pseudo-blit framebuffers here to get a resized version of vfb. VirtualFramebuffer *nvfb = FindDownloadTempBuffer(vfb); - BlitFramebuffer(nvfb, x, y, vfb, x, y, w, h, 0); - - PackFramebufferSync_(nvfb, x, y, w, h); + if (nvfb) { + BlitFramebuffer(nvfb, x, y, vfb, x, y, w, h, 0); + PackFramebufferSync_(nvfb, x, y, w, h); + } textureCache_->ForgetLastTexture(); RebindFramebuffer(); diff --git a/GPU/Common/FramebufferCommon.h b/GPU/Common/FramebufferCommon.h index f95a12670f..bf92ae189d 100644 --- a/GPU/Common/FramebufferCommon.h +++ b/GPU/Common/FramebufferCommon.h @@ -339,7 +339,7 @@ protected: void DownloadFramebufferOnSwitch(VirtualFramebuffer *vfb); void FindTransferFramebuffers(VirtualFramebuffer *&dstBuffer, VirtualFramebuffer *&srcBuffer, u32 dstBasePtr, int dstStride, int &dstX, int &dstY, u32 srcBasePtr, int srcStride, int &srcX, int &srcY, int &srcWidth, int &srcHeight, int &dstWidth, int &dstHeight, int bpp); VirtualFramebuffer *FindDownloadTempBuffer(VirtualFramebuffer *vfb); - virtual bool CreateDownloadTempBuffer(VirtualFramebuffer *nvfb) = 0; + virtual bool CreateDownloadTempBuffer(VirtualFramebuffer *nvfb); virtual void UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb) = 0; VirtualFramebuffer *CreateRAMFramebuffer(uint32_t fbAddress, int width, int height, int stride, GEBufferFormat format); diff --git a/GPU/Common/PostShader.cpp b/GPU/Common/PostShader.cpp index 734fa2484a..c8aacbf843 100644 --- a/GPU/Common/PostShader.cpp +++ b/GPU/Common/PostShader.cpp @@ -39,13 +39,17 @@ void LoadPostShaderInfo(std::vector directories) { std::vector notVisible; shaderInfo.clear(); - ShaderInfo off; + ShaderInfo off{}; + off.visible = true; off.name = "Off"; off.section = "Off"; - off.outputResolution = false; - off.isUpscalingFilter = false; - off.SSAAFilterLevel = 0; - off.requires60fps = false; + for (size_t i = 0; i < ARRAY_SIZE(off.settings); ++i) { + off.settings[i].name = ""; + off.settings[i].value = 0.0f; + off.settings[i].minValue = -1.0f; + off.settings[i].maxValue = 1.0f; + off.settings[i].step = 0.01f; + } shaderInfo.push_back(off); auto appendShader = [&](const ShaderInfo &info) { @@ -103,6 +107,21 @@ void LoadPostShaderInfo(std::vector directories) { section.Get("SSAA", &info.SSAAFilterLevel, 0); section.Get("60fps", &info.requires60fps, false); + for (size_t i = 0; i < ARRAY_SIZE(info.settings); ++i) { + auto &setting = info.settings[i]; + section.Get(StringFromFormat("SettingName%d", i + 1).c_str(), &setting.name, ""); + section.Get(StringFromFormat("SettingDefaultValue%d", i + 1).c_str(), &setting.value, 0.0f); + section.Get(StringFromFormat("SettingMinValue%d", i + 1).c_str(), &setting.minValue, -1.0f); + section.Get(StringFromFormat("SettingMaxValue%d", i + 1).c_str(), &setting.maxValue, 1.0f); + section.Get(StringFromFormat("SettingStep%d", i + 1).c_str(), &setting.step, 0.01f); + + // Populate the default setting value. + std::string section = StringFromFormat("%sSettingValue%d", info.section.c_str(), i + 1); + if (!setting.name.empty() && g_Config.mPostShaderSetting.find(section) == g_Config.mPostShaderSetting.end()) { + g_Config.mPostShaderSetting.insert(std::pair(section, setting.value)); + } + } + // Let's ignore shaders we can't support. TODO: Not a very good check if (gl_extensions.IsGLES && !gl_extensions.GLES3) { bool requiresIntegerSupport; diff --git a/GPU/Common/PostShader.h b/GPU/Common/PostShader.h index 62740a9569..86fd7b77da 100644 --- a/GPU/Common/PostShader.h +++ b/GPU/Common/PostShader.h @@ -44,6 +44,15 @@ struct ShaderInfo { // Force constant/max refresh for animated filters bool requires60fps; + struct Setting { + std::string name; + float value; + float maxValue; + float minValue; + float step; + }; + Setting settings[4]; + // TODO: Add support for all kinds of fun options like mapping the depth buffer, // SRGB texture reads, etc. diff --git a/GPU/Common/PresentationCommon.cpp b/GPU/Common/PresentationCommon.cpp index 3b486e4e53..c8b638f3ce 100644 --- a/GPU/Common/PresentationCommon.cpp +++ b/GPU/Common/PresentationCommon.cpp @@ -158,6 +158,11 @@ void PresentationCommon::CalculatePostShaderUniforms(int bufferWidth, int buffer // The shader translator tacks this onto our shaders, if we don't set it they render garbage. uniforms->gl_HalfPixel[0] = u_pixel_delta * 0.5f; uniforms->gl_HalfPixel[1] = v_pixel_delta * 0.5f; + + uniforms->setting[0] = g_Config.mPostShaderSetting[shaderInfo->section + "SettingValue1"];; + uniforms->setting[1] = g_Config.mPostShaderSetting[shaderInfo->section + "SettingValue2"]; + uniforms->setting[2] = g_Config.mPostShaderSetting[shaderInfo->section + "SettingValue3"]; + uniforms->setting[3] = g_Config.mPostShaderSetting[shaderInfo->section + "SettingValue4"]; } static std::string ReadShaderSrc(const std::string &filename) { @@ -221,7 +226,8 @@ bool PresentationCommon::BuildPostShader(const ShaderInfo *shaderInfo, const Sha { "u_texelDelta", 1, 1, Draw::UniformType::FLOAT2, offsetof(PostShaderUniforms, texelDelta) }, { "u_pixelDelta", 2, 2, Draw::UniformType::FLOAT2, offsetof(PostShaderUniforms, pixelDelta) }, { "u_time", 3, 3, Draw::UniformType::FLOAT4, offsetof(PostShaderUniforms, time) }, - { "u_video", 4, 4, Draw::UniformType::FLOAT1, offsetof(PostShaderUniforms, video) }, + { "u_setting", 4, 4, Draw::UniformType::FLOAT4, offsetof(PostShaderUniforms, setting) }, + { "u_video", 5, 5, Draw::UniformType::FLOAT1, offsetof(PostShaderUniforms, video) }, } }; Draw::Pipeline *pipeline = CreatePipeline({ vs, fs }, true, &postShaderDesc); if (!pipeline) diff --git a/GPU/Common/PresentationCommon.h b/GPU/Common/PresentationCommon.h index 5c6f546488..eae2834abf 100644 --- a/GPU/Common/PresentationCommon.h +++ b/GPU/Common/PresentationCommon.h @@ -31,6 +31,7 @@ struct CardboardSettings { struct PostShaderUniforms { float texelDelta[2]; float pixelDelta[2]; float time[4]; + float setting[4]; float video; float pad[3]; // Used on Direct3D9. float gl_HalfPixel[4]; diff --git a/GPU/Common/ShaderTranslation.cpp b/GPU/Common/ShaderTranslation.cpp index 7f42a7b2b7..192e59f186 100644 --- a/GPU/Common/ShaderTranslation.cpp +++ b/GPU/Common/ShaderTranslation.cpp @@ -86,6 +86,7 @@ cbuffer data : register(b0) { float2 u_texelDelta; float2 u_pixelDelta; float4 u_time; + float4 u_setting; float u_video; }; )"; @@ -101,6 +102,7 @@ layout (std140, set = 0, binding = 0) uniform Data { vec2 u_texelDelta; vec2 u_pixelDelta; vec4 u_time; + vec4 u_setting; float u_video; }; )"; @@ -110,7 +112,8 @@ float4 gl_HalfPixel : register(c0); float2 u_texelDelta : register(c1); float2 u_pixelDelta : register(c2); float4 u_time : register(c3); -float u_video : register(c4); +float4 u_setting : register(c4); +float u_video : register(c5); )"; // SPIRV-Cross' HLSL output has some deficiencies we need to work around. diff --git a/GPU/Common/VertexDecoderArm.cpp b/GPU/Common/VertexDecoderArm.cpp index 5467515e9f..f4fde1e097 100644 --- a/GPU/Common/VertexDecoderArm.cpp +++ b/GPU/Common/VertexDecoderArm.cpp @@ -285,7 +285,7 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int if (!CompileStep(dec, i)) { EndWrite(); // Reset the code ptr and return zero to indicate that we failed. - SetCodePtr(const_cast(start)); + ResetCodePtr(GetOffset(start)); char temp[1024] = {0}; dec.ToString(temp); INFO_LOG(G3D, "Could not compile vertex decoder: %s", temp); diff --git a/GPU/Common/VertexDecoderArm64.cpp b/GPU/Common/VertexDecoderArm64.cpp index 702dd6ae8d..a4714ee070 100644 --- a/GPU/Common/VertexDecoderArm64.cpp +++ b/GPU/Common/VertexDecoderArm64.cpp @@ -248,7 +248,7 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int if (!CompileStep(dec, i)) { EndWrite(); // Reset the code ptr (effectively undoing what we generated) and return zero to indicate that we failed. - SetCodePtr(const_cast(start)); + ResetCodePtr(GetOffset(start)); char temp[1024] = {0}; dec.ToString(temp); ERROR_LOG(G3D, "Could not compile vertex decoder, failed at step %d: %s", i, temp); diff --git a/GPU/Common/VertexDecoderX86.cpp b/GPU/Common/VertexDecoderX86.cpp index 1e63145628..e67b50fea0 100644 --- a/GPU/Common/VertexDecoderX86.cpp +++ b/GPU/Common/VertexDecoderX86.cpp @@ -251,7 +251,7 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int if (!CompileStep(dec, i)) { EndWrite(); // Reset the code ptr and return zero to indicate that we failed. - SetCodePtr(const_cast(start)); + ResetCodePtr(GetOffset(start)); return 0; } } diff --git a/GPU/D3D11/FramebufferManagerD3D11.cpp b/GPU/D3D11/FramebufferManagerD3D11.cpp index 6bca9211b9..70e2b19bfc 100644 --- a/GPU/D3D11/FramebufferManagerD3D11.cpp +++ b/GPU/D3D11/FramebufferManagerD3D11.cpp @@ -366,19 +366,6 @@ void FramebufferManagerD3D11::BindFramebufferAsColorTexture(int stage, VirtualFr } } -bool FramebufferManagerD3D11::CreateDownloadTempBuffer(VirtualFramebuffer *nvfb) { - nvfb->colorDepth = Draw::FBO_8888; - - nvfb->fbo = draw_->CreateFramebuffer({ nvfb->bufferWidth, nvfb->bufferHeight, 1, 1, true, (Draw::FBColorDepth)nvfb->colorDepth }); - if (!(nvfb->fbo)) { - ERROR_LOG(FRAMEBUF, "Error creating FBO! %i x %i", nvfb->renderWidth, nvfb->renderHeight); - return false; - } - - draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); - return true; -} - void FramebufferManagerD3D11::UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb) { // Nothing to do here. } diff --git a/GPU/D3D11/FramebufferManagerD3D11.h b/GPU/D3D11/FramebufferManagerD3D11.h index aa8641bb14..73b917f326 100644 --- a/GPU/D3D11/FramebufferManagerD3D11.h +++ b/GPU/D3D11/FramebufferManagerD3D11.h @@ -60,7 +60,6 @@ protected: // Used by ReadFramebufferToMemory and later framebuffer block copies void BlitFramebuffer(VirtualFramebuffer *dst, int dstX, int dstY, VirtualFramebuffer *src, int srcX, int srcY, int w, int h, int bpp) override; - bool CreateDownloadTempBuffer(VirtualFramebuffer *nvfb) override; void UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb) override; private: diff --git a/GPU/Directx9/FramebufferDX9.cpp b/GPU/Directx9/FramebufferDX9.cpp index 877b196e64..0300bbf45a 100644 --- a/GPU/Directx9/FramebufferDX9.cpp +++ b/GPU/Directx9/FramebufferDX9.cpp @@ -379,19 +379,6 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { } } - bool FramebufferManagerDX9::CreateDownloadTempBuffer(VirtualFramebuffer *nvfb) { - nvfb->colorDepth = Draw::FBO_8888; - - nvfb->fbo = draw_->CreateFramebuffer({ nvfb->bufferWidth, nvfb->bufferHeight, 1, 1, true, (Draw::FBColorDepth)nvfb->colorDepth }); - if (!(nvfb->fbo)) { - ERROR_LOG(FRAMEBUF, "Error creating FBO! %i x %i", nvfb->renderWidth, nvfb->renderHeight); - return false; - } - - draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); - return true; - } - void FramebufferManagerDX9::UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb) { // Nothing to do here. } diff --git a/GPU/Directx9/FramebufferDX9.h b/GPU/Directx9/FramebufferDX9.h index 3de91750c1..bab50d4dfc 100644 --- a/GPU/Directx9/FramebufferDX9.h +++ b/GPU/Directx9/FramebufferDX9.h @@ -71,7 +71,6 @@ protected: // Used by ReadFramebufferToMemory and later framebuffer block copies void BlitFramebuffer(VirtualFramebuffer *dst, int dstX, int dstY, VirtualFramebuffer *src, int srcX, int srcY, int w, int h, int bpp) override; - bool CreateDownloadTempBuffer(VirtualFramebuffer *nvfb) override; void UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb) override; private: diff --git a/GPU/GLES/DepthBufferGLES.cpp b/GPU/GLES/DepthBufferGLES.cpp index cf399cff87..534ee73d14 100644 --- a/GPU/GLES/DepthBufferGLES.cpp +++ b/GPU/GLES/DepthBufferGLES.cpp @@ -93,8 +93,7 @@ void FramebufferManagerGLES::PackDepthbuffer(VirtualFramebuffer *vfb, int x, int DEBUG_LOG(FRAMEBUF, "Reading depthbuffer to mem at %08x for vfb=%08x", z_address, vfb->fb_address); - // TODO: On desktop, we can just directly download, but for now testing. - const bool useColorPath = true; // gl_extensions.IsGLES; + const bool useColorPath = gl_extensions.IsGLES; bool format16Bit = false; if (useColorPath) { diff --git a/GPU/GLES/DrawEngineGLES.cpp b/GPU/GLES/DrawEngineGLES.cpp index 5587c93b54..ca32d8e508 100644 --- a/GPU/GLES/DrawEngineGLES.cpp +++ b/GPU/GLES/DrawEngineGLES.cpp @@ -426,7 +426,7 @@ void DrawEngineGLES::DoFlush() { // for a very minor speed boost. if (useElements) { size_t esz = sizeof(short) * indexGen.VertexCount(); - vai->ebo = render_->CreateBuffer(GL_ARRAY_BUFFER, esz, GL_STATIC_DRAW); + vai->ebo = render_->CreateBuffer(GL_ELEMENT_ARRAY_BUFFER, esz, GL_STATIC_DRAW); render_->BufferSubdata(vai->ebo, 0, esz, (uint8_t *)decIndex, false); } else { vai->ebo = 0; diff --git a/GPU/GLES/FramebufferManagerGLES.cpp b/GPU/GLES/FramebufferManagerGLES.cpp index 0800a8b917..266b4fde93 100644 --- a/GPU/GLES/FramebufferManagerGLES.cpp +++ b/GPU/GLES/FramebufferManagerGLES.cpp @@ -313,39 +313,11 @@ void FramebufferManagerGLES::BindFramebufferAsColorTexture(int stage, VirtualFra } } -bool FramebufferManagerGLES::CreateDownloadTempBuffer(VirtualFramebuffer *nvfb) { - // When updating VRAM, it need to be exact format. - if (!gstate_c.Supports(GPU_PREFER_CPU_DOWNLOAD)) { - switch (nvfb->format) { - case GE_FORMAT_4444: - nvfb->colorDepth = Draw::FBO_4444; - break; - case GE_FORMAT_5551: - nvfb->colorDepth = Draw::FBO_5551; - break; - case GE_FORMAT_565: - nvfb->colorDepth = Draw::FBO_565; - break; - case GE_FORMAT_8888: - default: - nvfb->colorDepth = Draw::FBO_8888; - break; - } - } - - nvfb->fbo = draw_->CreateFramebuffer({ nvfb->bufferWidth, nvfb->bufferHeight, 1, 1, false, (Draw::FBColorDepth)nvfb->colorDepth }); - if (!nvfb->fbo) { - ERROR_LOG(FRAMEBUF, "Error creating GL FBO! %i x %i", nvfb->renderWidth, nvfb->renderHeight); - return false; - } - return true; -} - void FramebufferManagerGLES::UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb) { _assert_msg_(G3D, nvfb->fbo, "Expecting a valid nvfb in UpdateDownloadTempBuffer"); // Discard the previous contents of this buffer where possible. - if (gl_extensions.GLES3 && glInvalidateFramebuffer != nullptr) { + if (gl_extensions.GLES3) { draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); } else if (gl_extensions.IsGLES) { draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); diff --git a/GPU/GLES/FramebufferManagerGLES.h b/GPU/GLES/FramebufferManagerGLES.h index 33d364fe7b..25f541fd81 100644 --- a/GPU/GLES/FramebufferManagerGLES.h +++ b/GPU/GLES/FramebufferManagerGLES.h @@ -64,7 +64,6 @@ protected: // Used by ReadFramebufferToMemory and later framebuffer block copies void BlitFramebuffer(VirtualFramebuffer *dst, int dstX, int dstY, VirtualFramebuffer *src, int srcX, int srcY, int w, int h, int bpp) override; - bool CreateDownloadTempBuffer(VirtualFramebuffer *nvfb) override; void UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb) override; private: diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 1ae3f9735d..c63bab6e40 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -2448,7 +2448,7 @@ void GPUCommon::DoState(PointerWrap &p) { DisplayList_v1 oldDL; p.Do(oldDL); // On 32-bit, they're the same, on 64-bit oldDL is bigger. - memcpy(&dls[i], &oldDL, sizeof(DisplayList)); + memcpy(&dls[i], &oldDL, sizeof(DisplayList_v1)); // Fix the other fields. Let's hope context wasn't important, it was a pointer. dls[i].context = 0; dls[i].offsetAddr = oldDL.offsetAddr; diff --git a/GPU/Software/SamplerX86.cpp b/GPU/Software/SamplerX86.cpp index a0c0a3ea91..5fdca729f8 100644 --- a/GPU/Software/SamplerX86.cpp +++ b/GPU/Software/SamplerX86.cpp @@ -79,7 +79,7 @@ NearestFunc SamplerJitCache::Compile(const SamplerID &id) { if (!Jit_ReadTextureFormat(id)) { EndWrite(); - SetCodePtr(const_cast(start)); + ResetCodePtr(GetOffset(start)); return nullptr; } @@ -106,7 +106,7 @@ LinearFunc SamplerJitCache::CompileLinear(const SamplerID &id) { if (!Jit_ReadTextureFormat(id)) { EndWrite(); - SetCodePtr(const_cast(nearest)); + ResetCodePtr(GetOffset(nearest)); return nullptr; } diff --git a/GPU/Vulkan/FramebufferVulkan.cpp b/GPU/Vulkan/FramebufferVulkan.cpp index 5bb733ddb0..dfef7d47b7 100644 --- a/GPU/Vulkan/FramebufferVulkan.cpp +++ b/GPU/Vulkan/FramebufferVulkan.cpp @@ -325,19 +325,6 @@ VkImageView FramebufferManagerVulkan::BindFramebufferAsColorTexture(int stage, V } } -bool FramebufferManagerVulkan::CreateDownloadTempBuffer(VirtualFramebuffer *nvfb) { - nvfb->colorDepth = Draw::FBO_8888; - - nvfb->fbo = draw_->CreateFramebuffer({ nvfb->bufferWidth, nvfb->bufferHeight, 1, 1, true, (Draw::FBColorDepth)nvfb->colorDepth }); - if (!(nvfb->fbo)) { - ERROR_LOG(FRAMEBUF, "Error creating FBO! %i x %i", nvfb->renderWidth, nvfb->renderHeight); - return false; - } - - draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); - return true; -} - void FramebufferManagerVulkan::UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb) { // Nothing to do here. } diff --git a/GPU/Vulkan/FramebufferVulkan.h b/GPU/Vulkan/FramebufferVulkan.h index d17d836216..bbd799e1d0 100644 --- a/GPU/Vulkan/FramebufferVulkan.h +++ b/GPU/Vulkan/FramebufferVulkan.h @@ -71,7 +71,6 @@ protected: // Used by ReadFramebufferToMemory and later framebuffer block copies void BlitFramebuffer(VirtualFramebuffer *dst, int dstX, int dstY, VirtualFramebuffer *src, int srcX, int srcY, int w, int h, int bpp) override; - bool CreateDownloadTempBuffer(VirtualFramebuffer *nvfb) override; void UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb) override; private: diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 6cd2493f5a..43de4d1078 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -231,6 +231,7 @@ void GPU_Vulkan::CheckGPUFeatures() { features |= GPU_SUPPORTS_INSTANCE_RENDERING; features |= GPU_SUPPORTS_VERTEX_TEXTURE_FETCH; features |= GPU_SUPPORTS_TEXTURE_FLOAT; + features |= GPU_PREFER_CPU_DOWNLOAD; if (vulkan_->GetDeviceFeatures().enabled.wideLines) { features |= GPU_SUPPORTS_WIDE_LINES; diff --git a/Qt/QtMain.cpp b/Qt/QtMain.cpp index 9782032d27..7713b6587d 100644 --- a/Qt/QtMain.cpp +++ b/Qt/QtMain.cpp @@ -48,6 +48,8 @@ static int browseFileEvent = -1; static int browseFolderEvent = -1; QTCamera *qtcamera = nullptr; +SDL_AudioSpec g_retFmt; + #ifdef SDL static SDL_AudioDeviceID audioDev = 0; @@ -56,35 +58,35 @@ extern void mixaudio(void *userdata, Uint8 *stream, int len) { } static void InitSDLAudioDevice() { - SDL_AudioSpec fmt, ret_fmt; + SDL_AudioSpec fmt; memset(&fmt, 0, sizeof(fmt)); fmt.freq = 44100; fmt.format = AUDIO_S16; fmt.channels = 2; - fmt.samples = 2048; + fmt.samples = 1024; fmt.callback = &mixaudio; fmt.userdata = nullptr; audioDev = 0; if (!g_Config.sAudioDevice.empty()) { - audioDev = SDL_OpenAudioDevice(g_Config.sAudioDevice.c_str(), 0, &fmt, &ret_fmt, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); + audioDev = SDL_OpenAudioDevice(g_Config.sAudioDevice.c_str(), 0, &fmt, &g_retFmt, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); if (audioDev <= 0) { WLOG("Failed to open preferred audio device %s", g_Config.sAudioDevice.c_str()); } } if (audioDev <= 0) { - audioDev = SDL_OpenAudioDevice(nullptr, 0, &fmt, &ret_fmt, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); + audioDev = SDL_OpenAudioDevice(nullptr, 0, &fmt, &g_retFmt, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); } if (audioDev <= 0) { ELOG("Failed to open audio: %s", SDL_GetError()); } else { - if (ret_fmt.samples != fmt.samples) // Notify, but still use it - ELOG("Output audio samples: %d (requested: %d)", ret_fmt.samples, fmt.samples); - if (ret_fmt.freq != fmt.freq || ret_fmt.format != fmt.format || ret_fmt.channels != fmt.channels) { + if (g_retFmt.samples != fmt.samples) // Notify, but still use it + ELOG("Output audio samples: %d (requested: %d)", g_retFmt.samples, fmt.samples); + if (g_retFmt.format != fmt.format || g_retFmt.channels != fmt.channels) { ELOG("Sound buffer format does not match requested format."); - ELOG("Output audio freq: %d (requested: %d)", ret_fmt.freq, fmt.freq); - ELOG("Output audio format: %d (requested: %d)", ret_fmt.format, fmt.format); - ELOG("Output audio channels: %d (requested: %d)", ret_fmt.channels, fmt.channels); + ELOG("Output audio freq: %d (requested: %d)", g_retFmt.freq, fmt.freq); + ELOG("Output audio format: %d (requested: %d)", g_retFmt.format, fmt.format); + ELOG("Output audio channels: %d (requested: %d)", g_retFmt.channels, fmt.channels); ELOG("Provided output format does not match requirement, turning audio off"); SDL_CloseAudioDevice(audioDev); } @@ -146,7 +148,9 @@ std::string System_GetProperty(SystemProperty prop) { int System_GetPropertyInt(SystemProperty prop) { switch (prop) { case SYSPROP_AUDIO_SAMPLE_RATE: - return 44100; + return g_retFmt.freq; + case SYSPROP_AUDIO_FRAMES_PER_BUFFER: + return g_retFmt.samples; case SYSPROP_DEVICE_TYPE: #if defined(__ANDROID__) return DEVICE_TYPE_MOBILE; diff --git a/Qt/mainwindow.cpp b/Qt/mainwindow.cpp index 35a4b2e278..b1c303f434 100644 --- a/Qt/mainwindow.cpp +++ b/Qt/mainwindow.cpp @@ -182,7 +182,7 @@ void MainWindow::lstateAct() if (dialog.exec()) { QStringList fileNames = dialog.selectedFiles(); - SaveState::Load(fileNames[0].toStdString(), SaveStateActionFinished, this); + SaveState::Load(fileNames[0].toStdString(), -1, SaveStateActionFinished, this); } } @@ -197,7 +197,7 @@ void MainWindow::sstateAct() if (dialog.exec()) { QStringList fileNames = dialog.selectedFiles(); - SaveState::Save(fileNames[0].toStdString(), SaveStateActionFinished, this); + SaveState::Save(fileNames[0].toStdString(), -1, SaveStateActionFinished, this); } } diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index 82651123ea..52028d0879 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -68,6 +68,8 @@ static int g_DesktopWidth = 0; static int g_DesktopHeight = 0; static float g_RefreshRate = 60.f; +static SDL_AudioSpec g_retFmt; + int getDisplayNumber(void) { int displayNumber = 0; char * displayNumberStr; @@ -82,21 +84,21 @@ int getDisplayNumber(void) { return displayNumber; } -extern void mixaudio(void *userdata, Uint8 *stream, int len) { - NativeMix((short *)stream, len / 4); +void sdl_mixaudio_callback(void *userdata, Uint8 *stream, int len) { + NativeMix((short *)stream, len / (2 * 2)); } static SDL_AudioDeviceID audioDev = 0; // Must be called after NativeInit(). static void InitSDLAudioDevice(const std::string &name = "") { - SDL_AudioSpec fmt, ret_fmt; + SDL_AudioSpec fmt; memset(&fmt, 0, sizeof(fmt)); fmt.freq = 44100; fmt.format = AUDIO_S16; fmt.channels = 2; - fmt.samples = 2048; - fmt.callback = &mixaudio; + fmt.samples = 1024; + fmt.callback = &sdl_mixaudio_callback; fmt.userdata = nullptr; std::string startDevice = name; @@ -106,24 +108,25 @@ static void InitSDLAudioDevice(const std::string &name = "") { audioDev = 0; if (!startDevice.empty()) { - audioDev = SDL_OpenAudioDevice(startDevice.c_str(), 0, &fmt, &ret_fmt, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); + audioDev = SDL_OpenAudioDevice(startDevice.c_str(), 0, &fmt, &g_retFmt, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); if (audioDev <= 0) { WLOG("Failed to open audio device: %s", startDevice.c_str()); } } if (audioDev <= 0) { - audioDev = SDL_OpenAudioDevice(nullptr, 0, &fmt, &ret_fmt, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); + ILOG("SDL: Trying a different device"); + audioDev = SDL_OpenAudioDevice(nullptr, 0, &fmt, &g_retFmt, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); } if (audioDev <= 0) { ELOG("Failed to open audio: %s", SDL_GetError()); } else { - if (ret_fmt.samples != fmt.samples) // Notify, but still use it - ELOG("Output audio samples: %d (requested: %d)", ret_fmt.samples, fmt.samples); - if (ret_fmt.freq != fmt.freq || ret_fmt.format != fmt.format || ret_fmt.channels != fmt.channels) { + if (g_retFmt.samples != fmt.samples) // Notify, but still use it + ELOG("Output audio samples: %d (requested: %d)", g_retFmt.samples, fmt.samples); + if (g_retFmt.format != fmt.format || g_retFmt.channels != fmt.channels) { ELOG("Sound buffer format does not match requested format."); - ELOG("Output audio freq: %d (requested: %d)", ret_fmt.freq, fmt.freq); - ELOG("Output audio format: %d (requested: %d)", ret_fmt.format, fmt.format); - ELOG("Output audio channels: %d (requested: %d)", ret_fmt.channels, fmt.channels); + ELOG("Output audio freq: %d (requested: %d)", g_retFmt.freq, fmt.freq); + ELOG("Output audio format: %d (requested: %d)", g_retFmt.format, fmt.format); + ELOG("Output audio channels: %d (requested: %d)", g_retFmt.channels, fmt.channels); ELOG("Provided output format does not match requirement, turning audio off"); SDL_CloseAudioDevice(audioDev); } @@ -319,7 +322,9 @@ std::string System_GetProperty(SystemProperty prop) { int System_GetPropertyInt(SystemProperty prop) { switch (prop) { case SYSPROP_AUDIO_SAMPLE_RATE: - return 44100; + return g_retFmt.freq; + case SYSPROP_AUDIO_FRAMES_PER_BUFFER: + return g_retFmt.samples; case SYSPROP_DEVICE_TYPE: #if defined(MOBILE_DEVICE) return DEVICE_TYPE_MOBILE; @@ -474,6 +479,8 @@ int main(int argc, char *argv[]) { printf("DEBUG: Vulkan is not available, not using Vulkan.\n"); } + SDL_version compiled; + SDL_version linked; int set_xres = -1; int set_yres = -1; int w = 0, h = 0; @@ -531,6 +538,15 @@ int main(int argc, char *argv[]) { } } + SDL_VERSION(&compiled); + SDL_GetVersion(&linked); + printf("Info: We compiled against SDL version %d.%d.%d", compiled.major, compiled.minor, compiled.patch); + if (compiled.minor != linked.minor || compiled.patch != linked.patch) { + printf(", but we are linking against SDL version %d.%d.%d., be aware that this can lead to unexpected behaviors\n", linked.major, linked.minor, linked.patch); + } else { + printf(" and we are linking against SDL version %d.%d.%d. :)\n", linked.major, linked.minor, linked.patch); + } + // Get the video info before doing anything else, so we don't get skewed resolution results. // TODO: support multiple displays correctly SDL_DisplayMode displayMode; @@ -842,6 +858,7 @@ int main(int argc, char *argv[]) { NativeKey(key); break; } +#if !SDL_VERSION_ATLEAST(2, 0, 10) // This behavior doesn't feel right on a macbook with a touchpad. #if !PPSSPP_PLATFORM(MAC) case SDL_FINGERMOTION: @@ -851,7 +868,6 @@ int main(int argc, char *argv[]) { touchEvent.motion.type = SDL_MOUSEMOTION; touchEvent.motion.timestamp = event.tfinger.timestamp; touchEvent.motion.windowID = SDL_GetWindowID(window); - touchEvent.motion.which = SDL_TOUCH_MOUSEID; touchEvent.motion.state = SDL_GetMouseState(NULL, NULL); touchEvent.motion.x = event.tfinger.x * w; touchEvent.motion.y = event.tfinger.y * h; @@ -868,7 +884,6 @@ int main(int argc, char *argv[]) { touchEvent.button.type = SDL_MOUSEBUTTONDOWN; touchEvent.button.timestamp = SDL_GetTicks(); touchEvent.button.windowID = SDL_GetWindowID(window); - touchEvent.button.which = SDL_TOUCH_MOUSEID; touchEvent.button.button = SDL_BUTTON_LEFT; touchEvent.button.state = SDL_PRESSED; touchEvent.button.clicks = 1; @@ -878,7 +893,6 @@ int main(int argc, char *argv[]) { touchEvent.motion.type = SDL_MOUSEMOTION; touchEvent.motion.timestamp = SDL_GetTicks(); touchEvent.motion.windowID = SDL_GetWindowID(window); - touchEvent.motion.which = SDL_TOUCH_MOUSEID; touchEvent.motion.x = event.tfinger.x * w; touchEvent.motion.y = event.tfinger.y * h; // Any real mouse cursor should also move @@ -897,7 +911,6 @@ int main(int argc, char *argv[]) { touchEvent.button.type = SDL_MOUSEBUTTONUP; touchEvent.button.timestamp = SDL_GetTicks(); touchEvent.button.windowID = SDL_GetWindowID(window); - touchEvent.button.which = SDL_TOUCH_MOUSEID; touchEvent.button.button = SDL_BUTTON_LEFT; touchEvent.button.state = SDL_RELEASED; touchEvent.button.clicks = 1; @@ -906,6 +919,7 @@ int main(int argc, char *argv[]) { SDL_PushEvent(&touchEvent); break; } +#endif #endif case SDL_MOUSEBUTTONDOWN: switch (event.button.button) { diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 1bc9d42267..f993d5aa34 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -502,7 +502,7 @@ void SystemInfoScreen::CreateViews() { deviceSpecs->Add(new ItemHeader(si->T("OS Information"))); deviceSpecs->Add(new InfoItem(si->T("Memory Page Size"), StringFromFormat(si->T("%d bytes"), GetMemoryProtectPageSize()))); deviceSpecs->Add(new InfoItem(si->T("RW/RX exclusive"), PlatformIsWXExclusive() ? di->T("Active") : di->T("Inactive"))); -#ifdef ANDROID +#if PPSSPP_PLATFORM(ANDROID) deviceSpecs->Add(new InfoItem(si->T("Sustained perf mode"), System_GetPropertyBool(SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE) ? di->T("Supported") : di->T("Unsupported"))); #endif @@ -512,14 +512,19 @@ void SystemInfoScreen::CreateViews() { #endif deviceSpecs->Add(new InfoItem(si->T("PPSSPP build"), build)); -#ifdef __ANDROID__ deviceSpecs->Add(new ItemHeader(si->T("Audio Information"))); deviceSpecs->Add(new InfoItem(si->T("Sample rate"), StringFromFormat("%d Hz", System_GetPropertyInt(SYSPROP_AUDIO_SAMPLE_RATE)))); - deviceSpecs->Add(new InfoItem(si->T("Frames per buffer"), StringFromFormat("%d", System_GetPropertyInt(SYSPROP_AUDIO_FRAMES_PER_BUFFER)))); + int framesPerBuffer = System_GetPropertyInt(SYSPROP_AUDIO_FRAMES_PER_BUFFER); + if (framesPerBuffer > 0) { + deviceSpecs->Add(new InfoItem(si->T("Frames per buffer"), StringFromFormat("%d", framesPerBuffer))); + } +#if PPSSPP_PLATFORM(ANDROID) deviceSpecs->Add(new InfoItem(si->T("Optimal sample rate"), StringFromFormat("%d Hz", System_GetPropertyInt(SYSPROP_AUDIO_OPTIMAL_SAMPLE_RATE)))); deviceSpecs->Add(new InfoItem(si->T("Optimal frames per buffer"), StringFromFormat("%d", System_GetPropertyInt(SYSPROP_AUDIO_OPTIMAL_FRAMES_PER_BUFFER)))); +#endif deviceSpecs->Add(new ItemHeader(si->T("Display Information"))); +#if PPSSPP_PLATFORM(ANDROID) deviceSpecs->Add(new InfoItem(si->T("Native Resolution"), StringFromFormat("%dx%d", System_GetPropertyInt(SYSPROP_DISPLAY_XRES), System_GetPropertyInt(SYSPROP_DISPLAY_YRES)))); diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index d4c035f1a2..f9293d2c78 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -412,7 +412,7 @@ void EmuScreen::sendMessage(const char *message, const char *value) { } else if (!strcmp(message, "boot")) { const char *ext = strrchr(value, '.'); if (ext != nullptr && !strcmp(ext, ".ppst")) { - SaveState::Load(value, &AfterStateBoot); + SaveState::Load(value, -1, &AfterStateBoot); } else { PSP_Shutdown(); bootPending_ = true; @@ -1288,19 +1288,8 @@ static void DrawDebugStats(DrawBuffer *draw2d, const Bounds &bounds) { static void DrawAudioDebugStats(DrawBuffer *draw2d, const Bounds &bounds) { FontID ubuntu24("UBUNTU24"); - char statbuf[1024] = { 0 }; - const AudioDebugStats *stats = __AudioGetDebugStats(); - snprintf(statbuf, sizeof(statbuf), - "Audio buffer: %d/%d (low watermark: %d)\n" - "Underruns: %d\n" - "Overruns: %d\n" - "Sample rate: %d\n" - "Push size: %d\n", - stats->buffered, stats->bufsize, stats->watermark, - stats->underrunCount, - stats->overrunCount, - stats->instantSampleRate, - stats->lastPushSize); + char statbuf[4096] = { 0 }; + __AudioGetDebugStats(statbuf, sizeof(statbuf)); draw2d->SetFontScale(0.7f, 0.7f); draw2d->DrawTextRect(ubuntu24, statbuf, bounds.x + 11, bounds.y + 31, bounds.w - 20, bounds.h - 30, 0xc0000000, FLAG_DYNAMIC_ASCII | FLAG_WRAP_TEXT); draw2d->DrawTextRect(ubuntu24, statbuf, bounds.x + 10, bounds.y + 30, bounds.w - 20, bounds.h - 30, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII | FLAG_WRAP_TEXT); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 8ab1710d45..798a5868ab 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -292,6 +292,17 @@ void GameSettingsScreen::CreateViews() { return g_Config.iRenderingMode != FB_NON_BUFFERED_MODE; }); + auto shaderChain = GetPostShaderChain(g_Config.sPostShaderName); + for (auto shaderInfo : shaderChain) { + for (size_t i = 0; i < ARRAY_SIZE(shaderInfo->settings); ++i) { + auto &setting = shaderInfo->settings[i]; + if (!setting.name.empty()) { + auto &value = g_Config.mPostShaderSetting[StringFromFormat("%sSettingValue%d", shaderInfo->section.c_str(), i + 1)]; + graphicsSettings->Add(new PopupSliderChoiceFloat(&value, setting.minValue, setting.maxValue, ps->T(setting.name), setting.step, screenManager())); + } + } + } + #if !defined(MOBILE_DEVICE) graphicsSettings->Add(new CheckBox(&g_Config.bFullScreen, gr->T("FullScreen", "Full Screen")))->OnClick.Handle(this, &GameSettingsScreen::OnFullscreenChange); if (System_GetPropertyInt(SYSPROP_DISPLAY_COUNT) > 1) { @@ -574,10 +585,6 @@ void GameSettingsScreen::CreateViews() { CheckBox *extraAudio = audioSettings->Add(new CheckBox(&g_Config.bExtraAudioBuffering, a->T("AudioBufferingForBluetooth", "Bluetooth-friendly buffer (slower)"))); extraAudio->SetEnabledPtr(&g_Config.bEnableSound); //#endif - if (System_GetPropertyInt(SYSPROP_AUDIO_SAMPLE_RATE) == 44100) { - CheckBox *resampling = audioSettings->Add(new CheckBox(&g_Config.bAudioResampler, a->T("Audio sync", "Audio sync (resampling)"))); - resampling->SetEnabledPtr(&g_Config.bEnableSound); - } PopupSliderChoice *sasVol = audioSettings->Add(new PopupSliderChoice(&g_Config.iSASVolume, 0, MAX_CONFIG_VOLUME, a->T("SAS volume"), screenManager())); sasVol->SetEnabledPtr(&g_Config.bEnableSound); @@ -1480,6 +1487,7 @@ UI::EventReturn GameSettingsScreen::OnPostProcShader(UI::EventParams &e) { UI::EventReturn GameSettingsScreen::OnPostProcShaderChange(UI::EventParams &e) { NativeMessageReceived("gpu_resized", ""); + RecreateViews(); // Update setting name return UI::EVENT_DONE; } diff --git a/UI/GamepadEmu.cpp b/UI/GamepadEmu.cpp index 4ea9a6d7a7..ef3a1ad2be 100644 --- a/UI/GamepadEmu.cpp +++ b/UI/GamepadEmu.cpp @@ -490,6 +490,138 @@ void PSPStick::ProcessTouch(float x, float y, bool down) { } } +PSPCustomStick::PSPCustomStick(ImageID bgImg, ImageID stickImg, ImageID stickDownImg, float scale, UI::LayoutParams *layoutParams) + : PSPStick(bgImg, stickImg, stickDownImg, -1, scale, layoutParams) { +} + +void PSPCustomStick::Draw(UIContext &dc) { + float opacity = GetButtonOpacity(); + if (opacity <= 0.0f) + return; + + if (dragPointerId_ != -1 && g_Config.iTouchButtonStyle == 2) { + opacity *= 1.35f; + } + + uint32_t colorBg = colorAlpha(GetButtonColor(), opacity); + uint32_t downBg = colorAlpha(0x00FFFFFF, opacity * 0.5f); + uint32_t color = colorAlpha(0x808080, opacity); + + if (centerX_ < 0.0f) { + centerX_ = bounds_.centerX(); + centerY_ = bounds_.centerY(); + } + + float stickX = centerX_; + float stickY = centerY_; + + float dx, dy; + dx = posX_; + dy = -posY_; + + dc.Draw()->DrawImage(bgImg_, stickX, stickY, 1.0f * scale_, colorBg, ALIGN_CENTER); + if (dragPointerId_ != -1 && g_Config.iTouchButtonStyle == 2 && stickDownImg_ != stickImageIndex_) + dc.Draw()->DrawImage(stickDownImg_, stickX + dx * stick_size_ * scale_, stickY - dy * stick_size_ * scale_, 1.0f * scale_, downBg, ALIGN_CENTER); + dc.Draw()->DrawImage(stickImageIndex_, stickX + dx * stick_size_ * scale_, stickY - dy * stick_size_ * scale_, 1.0f * scale_, colorBg, ALIGN_CENTER); +} + +void PSPCustomStick::Touch(const TouchInput &input) { + GamepadView::Touch(input); + if (input.flags & TOUCH_RELEASE_ALL) { + dragPointerId_ = -1; + centerX_ = bounds_.centerX(); + centerY_ = bounds_.centerY(); + posX_ = 0.0f; + posY_ = 0.0f; + return; + } + if (input.flags & TOUCH_DOWN) { + if (dragPointerId_ == -1 && bounds_.Contains(input.x, input.y)) { + if (g_Config.bAutoCenterTouchAnalog) { + centerX_ = input.x; + centerY_ = input.y; + } else { + centerX_ = bounds_.centerX(); + centerY_ = bounds_.centerY(); + } + dragPointerId_ = input.id; + ProcessTouch(input.x, input.y, true); + } + } + if (input.flags & TOUCH_MOVE) { + if (input.id == dragPointerId_) { + ProcessTouch(input.x, input.y, true); + } + } + if (input.flags & TOUCH_UP) { + if (input.id == dragPointerId_) { + dragPointerId_ = -1; + centerX_ = bounds_.centerX(); + centerY_ = bounds_.centerY(); + ProcessTouch(input.x, input.y, false); + } + } +} + +void PSPCustomStick::ProcessTouch(float x, float y, bool down) { + static const int button[16] = {CTRL_LTRIGGER, CTRL_RTRIGGER, CTRL_SQUARE, CTRL_TRIANGLE, CTRL_CIRCLE, CTRL_CROSS, CTRL_UP, CTRL_DOWN, CTRL_LEFT, CTRL_RIGHT, CTRL_START, CTRL_SELECT}; + + if (down && centerX_ >= 0.0f) { + float inv_stick_size = 1.0f / (stick_size_ * scale_); + + float dx = (x - centerX_) * inv_stick_size; + float dy = (y - centerY_) * inv_stick_size; + + dx = std::min(1.0f, std::max(-1.0f, dx)); + dy = std::min(1.0f, std::max(-1.0f, dy)); + + if (g_Config.iRightAnalogRight != 0) { + if (dx > 0.5f) + __CtrlButtonDown(button[g_Config.iRightAnalogRight-1]); + else + __CtrlButtonUp(button[g_Config.iRightAnalogRight-1]); + } + if (g_Config.iRightAnalogLeft != 0) { + if (dx < -0.5f) + __CtrlButtonDown(button[g_Config.iRightAnalogLeft-1]); + else + __CtrlButtonUp(button[g_Config.iRightAnalogLeft-1]); + } + if (g_Config.iRightAnalogUp != 0) { + if (dy < -0.5f) + __CtrlButtonDown(button[g_Config.iRightAnalogUp-1]); + else + __CtrlButtonUp(button[g_Config.iRightAnalogUp-1]); + } + if (g_Config.iRightAnalogDown != 0) { + if (dy > 0.5f) + __CtrlButtonDown(button[g_Config.iRightAnalogDown-1]); + else + __CtrlButtonUp(button[g_Config.iRightAnalogDown-1]); + } + if (g_Config.iRightAnalogPress != 0) + __CtrlButtonDown(button[g_Config.iRightAnalogPress-1]); + + posX_ = dx; + posY_ = dy; + + } else { + if (g_Config.iRightAnalogUp != 0) + __CtrlButtonUp(button[g_Config.iRightAnalogUp-1]); + if (g_Config.iRightAnalogDown != 0) + __CtrlButtonUp(button[g_Config.iRightAnalogDown-1]); + if (g_Config.iRightAnalogLeft != 0) + __CtrlButtonUp(button[g_Config.iRightAnalogLeft-1]); + if (g_Config.iRightAnalogRight != 0) + __CtrlButtonUp(button[g_Config.iRightAnalogRight-1]); + if (g_Config.iRightAnalogPress != 0) + __CtrlButtonUp(button[g_Config.iRightAnalogPress-1]); + + posX_ = 0.0f; + posY_ = 0.0f; + } +} + void InitPadLayout(float xres, float yres, float globalScale) { const float scale = globalScale; const int halfW = xres / 2; @@ -720,8 +852,12 @@ UI::ViewGroup *CreatePadLayout(float xres, float yres, bool *pause) { if (g_Config.touchAnalogStick.show) root->Add(new PSPStick(stickBg, stickImage, ImageID("I_STICK"), 0, g_Config.touchAnalogStick.scale, buttonLayoutParams(g_Config.touchAnalogStick))); - if (g_Config.touchRightAnalogStick.show) - root->Add(new PSPStick(stickBg, stickImage, ImageID("I_STICK"), 1, g_Config.touchRightAnalogStick.scale, buttonLayoutParams(g_Config.touchRightAnalogStick))); + if (g_Config.touchRightAnalogStick.show) { + if (g_Config.bRightAnalogCustom) + root->Add(new PSPCustomStick(stickBg, stickImage, ImageID("I_STICK"), g_Config.touchRightAnalogStick.scale, buttonLayoutParams(g_Config.touchRightAnalogStick))); + else + root->Add(new PSPStick(stickBg, stickImage, ImageID("I_STICK"), 1, g_Config.touchRightAnalogStick.scale, buttonLayoutParams(g_Config.touchRightAnalogStick))); + } addComboKey(g_Config.iCombokey0, g_Config.bComboToggle0, roundImage, ImageID("I_ROUND"), comboKeyImages[0], g_Config.touchCombo0); addComboKey(g_Config.iCombokey1, g_Config.bComboToggle1, roundImage, ImageID("I_ROUND"), comboKeyImages[1], g_Config.touchCombo1); diff --git a/UI/GamepadEmu.h b/UI/GamepadEmu.h index 5e0cf4c6e4..9f2fbc2a1f 100644 --- a/UI/GamepadEmu.h +++ b/UI/GamepadEmu.h @@ -160,9 +160,7 @@ public: void Draw(UIContext &dc) override; void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; -private: - void ProcessTouch(float x, float y, bool down); - +protected: int dragPointerId_; ImageID bgImg_; ImageID stickImageIndex_; @@ -174,6 +172,23 @@ private: float centerX_; float centerY_; + +private: + void ProcessTouch(float x, float y, bool down); +}; + +class PSPCustomStick : public PSPStick { +public: + PSPCustomStick(ImageID bgImg, ImageID stickImg, ImageID stickDownImg, float scale, UI::LayoutParams *layoutParams); + + void Touch(const TouchInput &input) override; + void Draw(UIContext &dc) override; + +private: + void ProcessTouch(float x, float y, bool down); + + float posX_ = 0.0f; + float posY_ = 0.0f; }; //initializes the layout from Config. if a default layout does not exist, diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index d562a3b59f..80dfe61361 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -601,7 +601,7 @@ void GameBrowser::Draw(UIContext &dc) { } } -static bool IsValidPBP(const std::string &path) { +static bool IsValidPBP(const std::string &path, bool allowHomebrew) { if (!File::Exists(path)) return false; @@ -613,7 +613,7 @@ static bool IsValidPBP(const std::string &path) { ParamSFOData sfo; sfo.ReadSFO(sfoData); - if (sfo.GetValueString("DISC_ID").empty()) + if (!allowHomebrew && sfo.GetValueString("DISC_ID").empty()) return false; if (sfo.GetValueString("CATEGORY") == "ME") @@ -628,7 +628,6 @@ void GameBrowser::Refresh() { lastScale_ = g_Config.fGameGridScale; lastLayoutWasGrid_ = *gridStyle_; - homebrewStoreButton_ = nullptr; // Kill all the contents Clear(); @@ -706,7 +705,7 @@ void GameBrowser::Refresh() { bool isGame = !fileInfo[i].isDirectory; bool isSaveData = false; // Check if eboot directory - if (!isGame && path_.GetPath().size() >= 4 && IsValidPBP(path_.GetPath() + fileInfo[i].name + "/EBOOT.PBP")) + if (!isGame && path_.GetPath().size() >= 4 && IsValidPBP(path_.GetPath() + fileInfo[i].name + "/EBOOT.PBP", true)) isGame = true; else if (!isGame && File::Exists(path_.GetPath() + fileInfo[i].name + "/PSP_GAME/SYSDIR")) isGame = true; @@ -785,9 +784,7 @@ void GameBrowser::Refresh() { if (browseFlags_ & BrowseFlags::HOMEBREW_STORE) { Add(new Spacer()); - homebrewStoreButton_ = Add(new Choice(mm->T("DownloadFromStore", "Download from the PPSSPP Homebrew Store"), new UI::LinearLayoutParams(UI::WRAP_CONTENT, UI::WRAP_CONTENT))); - } else { - homebrewStoreButton_ = nullptr; + Add(new Choice(mm->T("DownloadFromStore", "Download from the PPSSPP Homebrew Store"), new UI::LinearLayoutParams(UI::WRAP_CONTENT, UI::WRAP_CONTENT)))->OnClick.Handle(this, &GameBrowser::OnHomebrewStore); } if (!lastText_.empty() && gameButtons.empty()) { @@ -908,7 +905,12 @@ UI::EventReturn GameBrowser::OnRecentClear(UI::EventParams &e) { return UI::EVENT_DONE; } -MainScreen::MainScreen() : highlightProgress_(0.0f), prevHighlightProgress_(0.0f), backFromStore_(false), lockBackgroundAudio_(false) { +UI::EventReturn GameBrowser::OnHomebrewStore(UI::EventParams &e) { + screenManager_->push(new StoreScreen()); + return UI::EVENT_DONE; +} + +MainScreen::MainScreen() { System_SendMessage("event", "mainscreen"); SetBackgroundAudioGame(""); lastVertical_ = UseVerticalLayout(); @@ -918,7 +920,6 @@ MainScreen::~MainScreen() { SetBackgroundAudioGame(""); } - void MainScreen::CreateViews() { // Information in the top left. // Back button to the bottom left. @@ -974,11 +975,6 @@ void MainScreen::CreateViews() { mm->T("How to get homebrew & demos", "How to get homebrew && demos"), "https://www.ppsspp.org/gethomebrew.html", new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); - Choice *hbStore = tabHomebrew->HomebrewStoreButton(); - if (hbStore) { - hbStore->OnClick.Handle(this, &MainScreen::OnHomebrewStore); - } - scrollAllGames->Add(tabAllGames); gameBrowsers_.push_back(tabAllGames); scrollHomebrew->Add(tabHomebrew); @@ -1327,11 +1323,6 @@ UI::EventReturn MainScreen::OnCredits(UI::EventParams &e) { return UI::EVENT_DONE; } -UI::EventReturn MainScreen::OnHomebrewStore(UI::EventParams &e) { - screenManager()->push(new StoreScreen()); - return UI::EVENT_DONE; -} - UI::EventReturn MainScreen::OnSupport(UI::EventParams &e) { #ifdef __ANDROID__ LaunchBrowser("market://details?id=org.ppsspp.ppssppgold"); diff --git a/UI/MainScreen.h b/UI/MainScreen.h index c731315bf9..9f90ea7bb6 100644 --- a/UI/MainScreen.h +++ b/UI/MainScreen.h @@ -53,8 +53,6 @@ public: UI::Event OnHoldChoice; UI::Event OnHighlight; - UI::Choice *HomebrewStoreButton() { return homebrewStoreButton_; } - void FocusGame(const std::string &gamePath); void SetPath(const std::string &path); void Draw(UIContext &dc) override; @@ -81,14 +79,14 @@ private: UI::EventReturn PinToggleClick(UI::EventParams &e); UI::EventReturn GridSettingsClick(UI::EventParams &e); UI::EventReturn OnRecentClear(UI::EventParams &e); + UI::EventReturn OnHomebrewStore(UI::EventParams &e); UI::ViewGroup *gameList_ = nullptr; PathBrowser path_; - bool *gridStyle_; + bool *gridStyle_ = nullptr; BrowseFlags browseFlags_; std::string lastText_; std::string lastLink_; - UI::Choice *homebrewStoreButton_ = nullptr; std::string focusGamePath_; bool listingPending_ = false; float lastScale_ = 1.0f; @@ -132,21 +130,20 @@ protected: UI::EventReturn OnExit(UI::EventParams &e); UI::EventReturn OnDownloadUpgrade(UI::EventParams &e); UI::EventReturn OnDismissUpgrade(UI::EventParams &e); - UI::EventReturn OnHomebrewStore(UI::EventParams &e); UI::EventReturn OnAllowStorage(UI::EventParams &e); - UI::LinearLayout *upgradeBar_; - UI::TabHolder *tabHolder_; + UI::LinearLayout *upgradeBar_ = nullptr; + UI::TabHolder *tabHolder_ = nullptr; std::string restoreFocusGamePath_; std::vector gameBrowsers_; std::string highlightedGamePath_; std::string prevHighlightedGamePath_; - float highlightProgress_; - float prevHighlightProgress_; - bool backFromStore_; - bool lockBackgroundAudio_; + float highlightProgress_ = 0.0f; + float prevHighlightProgress_ = 0.0f; + bool backFromStore_ = false; + bool lockBackgroundAudio_ = false; bool lastVertical_; bool confirmedTemporary_ = false; diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index a0da9229c7..41ff959674 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -710,7 +710,7 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch #endif if (!boot_filename.empty() && stateToLoad != NULL) { - SaveState::Load(stateToLoad, [](SaveState::Status status, const std::string &message, void *) { + SaveState::Load(stateToLoad, -1, [](SaveState::Status status, const std::string &message, void *) { if (!message.empty() && (!g_Config.bDumpFrames || !g_Config.bDumpVideoOutput)) { osm.Show(message, status == SaveState::Status::SUCCESS ? 2.0 : 5.0); } diff --git a/UI/Store.cpp b/UI/Store.cpp index 9b733a07ef..b94dc9bab2 100644 --- a/UI/Store.cpp +++ b/UI/Store.cpp @@ -244,6 +244,7 @@ void ProductView::CreateViews() { auto di = GetI18NCategory("Dialog"); wasInstalled_ = IsGameInstalled(); if (!wasInstalled_) { + launchButton_ = nullptr; installButton_ = Add(new Button(st->T("Install"))); installButton_->OnClick.Handle(this, &ProductView::OnInstall); } else { diff --git a/UI/TouchControlVisibilityScreen.cpp b/UI/TouchControlVisibilityScreen.cpp index 31cd45d761..9923b7bf9e 100644 --- a/UI/TouchControlVisibilityScreen.cpp +++ b/UI/TouchControlVisibilityScreen.cpp @@ -70,6 +70,7 @@ void TouchControlVisibilityScreen::CreateViews() { gridsettings.fillCells = true; GridLayout *grid = vert->Add(new GridLayout(gridsettings, new LayoutParams(FILL_PARENT, WRAP_CONTENT))); + static const char* rightAnalogKey = "Right Analog Stick (tap to customize)"; toggles_.clear(); toggles_.push_back({ "Circle", &g_Config.bShowTouchCircle, ImageID("I_CIRCLE") }); toggles_.push_back({ "Cross", &g_Config.bShowTouchCross, ImageID("I_CROSS") }); @@ -81,7 +82,7 @@ void TouchControlVisibilityScreen::CreateViews() { toggles_.push_back({ "Select", &g_Config.touchSelectKey.show, ImageID("I_SELECT") }); toggles_.push_back({ "Dpad", &g_Config.touchDpad.show, ImageID::invalid() }); toggles_.push_back({ "Analog Stick", &g_Config.touchAnalogStick.show, ImageID::invalid() }); - toggles_.push_back({ "Right Analog Stick\n(not used by most games)", &g_Config.touchRightAnalogStick.show, ImageID::invalid() }); + toggles_.push_back({ rightAnalogKey, &g_Config.touchRightAnalogStick.show, ImageID::invalid() }); toggles_.push_back({ "Unthrottle", &g_Config.touchUnthrottleKey.show, ImageID::invalid() }); toggles_.push_back({ "Combo0", &g_Config.touchCombo0.show, ImageID("I_1") }); toggles_.push_back({ "Combo1", &g_Config.touchCombo1.show, ImageID("I_2") }); @@ -95,7 +96,6 @@ void TouchControlVisibilityScreen::CreateViews() { toggles_.push_back({ "Auto Analog Rotation (CCW)", &g_Config.touchAnalogRotationCCWKey.show, ImageID::invalid() }); auto mc = GetI18NCategory("MappableControls"); - for (auto toggle : toggles_) { LinearLayout *row = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); row->SetSpacing(0); @@ -103,16 +103,20 @@ void TouchControlVisibilityScreen::CreateViews() { CheckBox *checkbox = new CheckBox(toggle.show, "", "", new LinearLayoutParams(50, WRAP_CONTENT)); row->Add(checkbox); - Choice *choice; - if (toggle.img.isValid()) { - choice = new CheckBoxChoice(toggle.img, checkbox, new LinearLayoutParams(1.0f)); + if (toggle.key == rightAnalogKey) { + Choice *rightAnalog = new Choice(co->T(rightAnalogKey), "", false, new LinearLayoutParams(1.0f)); + rightAnalog->SetCentered(true); + row->Add(rightAnalog)->OnClick.Handle(this, &TouchControlVisibilityScreen::RightAnalogBindScreen); } else { - choice = new CheckBoxChoice(mc->T(toggle.key), checkbox, new LinearLayoutParams(1.0f)); + Choice *choice; + if (toggle.img.isValid()) { + choice = new CheckBoxChoice(toggle.img, checkbox, new LinearLayoutParams(1.0f)); + } else { + choice = new CheckBoxChoice(mc->T(toggle.key), checkbox, new LinearLayoutParams(1.0f)); + } + choice->SetCentered(true); + row->Add(choice); } - - choice->SetCentered(true); - - row->Add(choice); grid->Add(row); } } @@ -121,6 +125,38 @@ void TouchControlVisibilityScreen::onFinish(DialogResult result) { g_Config.Save("TouchControlVisibilityScreen::onFinish"); } +void RightAnalogMappingScreen::CreateViews() { + using namespace UI; + + auto di = GetI18NCategory("Dialog"); + auto co = GetI18NCategory("Controls"); + + root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT)); + Choice *back = new Choice(di->T("Back"), "", false, new AnchorLayoutParams(leftColumnWidth - 10, WRAP_CONTENT, 10, NONE, NONE, 10)); + root_->Add(back)->OnClick.Handle(this, &UIScreen::OnBack); + TabHolder *tabHolder = new TabHolder(ORIENT_VERTICAL, leftColumnWidth, new AnchorLayoutParams(10, 0, 10, 0, false)); + root_->Add(tabHolder); + ScrollView *rightPanel = new ScrollView(ORIENT_VERTICAL); + tabHolder->AddTab(co->T("Binds"), rightPanel); + LinearLayout *vert = rightPanel->Add(new LinearLayout(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, FILL_PARENT))); + vert->SetSpacing(0); + + static const char *rightAnalogButton[] = {"None", "Left trigger", "Right trigger", "Square", "Triangle", "Circle", "Cross", "D-PAD Up", "D-PAD Down", "D-PAD Left", "D-PAD Right", "Start", "Select"}; + + vert->Add(new CheckBox(&g_Config.bRightAnalogCustom, co->T("Use custom right analog"))); + PopupMultiChoice *rightAnalogUp = vert->Add(new PopupMultiChoice(&g_Config.iRightAnalogUp, co->T("Right analog up"), rightAnalogButton, 0, ARRAY_SIZE(rightAnalogButton), co->GetName(), screenManager())); + PopupMultiChoice *rightAnalogDown = vert->Add(new PopupMultiChoice(&g_Config.iRightAnalogDown, co->T("Right analog down"), rightAnalogButton, 0, ARRAY_SIZE(rightAnalogButton), co->GetName(), screenManager())); + PopupMultiChoice *rightAnalogLeft = vert->Add(new PopupMultiChoice(&g_Config.iRightAnalogLeft, co->T("Right analog left"), rightAnalogButton, 0, ARRAY_SIZE(rightAnalogButton), co->GetName(), screenManager())); + PopupMultiChoice *rightAnalogRight = vert->Add(new PopupMultiChoice(&g_Config.iRightAnalogRight, co->T("Right analog right"), rightAnalogButton, 0, ARRAY_SIZE(rightAnalogButton), co->GetName(), screenManager())); + PopupMultiChoice *rightAnalogPress = vert->Add(new PopupMultiChoice(&g_Config.iRightAnalogPress, co->T("Keep this button pressed when right analog is pressed"), rightAnalogButton, 0, ARRAY_SIZE(rightAnalogButton), co->GetName(), screenManager())); + vert->Add(new CheckBox(&g_Config.touchRightAnalogStick.show, co->T("Show right analog"))); + rightAnalogUp->SetEnabledPtr(&g_Config.bRightAnalogCustom); + rightAnalogDown->SetEnabledPtr(&g_Config.bRightAnalogCustom); + rightAnalogLeft->SetEnabledPtr(&g_Config.bRightAnalogCustom); + rightAnalogRight->SetEnabledPtr(&g_Config.bRightAnalogCustom); + rightAnalogPress->SetEnabledPtr(&g_Config.bRightAnalogCustom); +} + UI::EventReturn TouchControlVisibilityScreen::OnToggleAll(UI::EventParams &e) { for (auto toggle : toggles_) { *toggle.show = nextToggleAll_; @@ -130,6 +166,12 @@ UI::EventReturn TouchControlVisibilityScreen::OnToggleAll(UI::EventParams &e) { return UI::EVENT_DONE; } +UI::EventReturn TouchControlVisibilityScreen::RightAnalogBindScreen(UI::EventParams &e) { + screenManager()->push(new RightAnalogMappingScreen()); + + return UI::EVENT_DONE; +} + UI::EventReturn CheckBoxChoice::HandleClick(UI::EventParams &e) { checkbox_->Toggle(); diff --git a/UI/TouchControlVisibilityScreen.h b/UI/TouchControlVisibilityScreen.h index 82efd442b3..895078626b 100644 --- a/UI/TouchControlVisibilityScreen.h +++ b/UI/TouchControlVisibilityScreen.h @@ -37,8 +37,14 @@ public: protected: UI::EventReturn OnToggleAll(UI::EventParams &e); + UI::EventReturn RightAnalogBindScreen(UI::EventParams &e); private: std::vector toggles_; bool nextToggleAll_ = true; }; + +class RightAnalogMappingScreen : public UIDialogScreenWithBackground { +public: + void CreateViews() override; +}; diff --git a/UWP/CommonUWP/CommonUWP.vcxproj b/UWP/CommonUWP/CommonUWP.vcxproj index 4eb97efe92..a5fe599d10 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj +++ b/UWP/CommonUWP/CommonUWP.vcxproj @@ -383,9 +383,6 @@ - - - @@ -476,4 +473,4 @@ - \ No newline at end of file + diff --git a/UWP/CommonUWP/CommonUWP.vcxproj.filters b/UWP/CommonUWP/CommonUWP.vcxproj.filters index 67086f1bc2..566049b279 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj.filters +++ b/UWP/CommonUWP/CommonUWP.vcxproj.filters @@ -53,9 +53,6 @@ - - - @@ -99,4 +96,4 @@ Crypto - \ No newline at end of file + diff --git a/Windows/MainWindowMenu.cpp b/Windows/MainWindowMenu.cpp index 827f535dbd..8d3b25fa9c 100644 --- a/Windows/MainWindowMenu.cpp +++ b/Windows/MainWindowMenu.cpp @@ -649,14 +649,14 @@ namespace MainWindow { case ID_FILE_LOADSTATEFILE: if (W32Util::BrowseForFileName(true, hWnd, L"Load state", 0, L"Save States (*.ppst)\0*.ppst\0All files\0*.*\0\0", L"ppst", fn)) { SetCursor(LoadCursor(0, IDC_WAIT)); - SaveState::Load(fn, SaveStateActionFinished); + SaveState::Load(fn, -1, SaveStateActionFinished); } break; case ID_FILE_SAVESTATEFILE: if (W32Util::BrowseForFileName(false, hWnd, L"Save state", 0, L"Save States (*.ppst)\0*.ppst\0All files\0*.*\0\0", L"ppst", fn)) { SetCursor(LoadCursor(0, IDC_WAIT)); - SaveState::Save(fn, SaveStateActionFinished); + SaveState::Save(fn, -1, SaveStateActionFinished); } break; diff --git a/assets/shaders/bloom.fsh b/assets/shaders/bloom.fsh index c2450c2709..e0636297a2 100644 --- a/assets/shaders/bloom.fsh +++ b/assets/shaders/bloom.fsh @@ -10,8 +10,7 @@ precision mediump int; uniform sampler2D sampler0; varying vec2 v_texcoord0; -float amount = 0.60; // suitable range = 0.00 - 1.00 -float power = 0.5; // suitable range = 0.0 - 1.0 +uniform vec4 u_setting; void main() { @@ -21,9 +20,9 @@ void main() for(int i= -3 ;i < 3; i++) { - sum += texture2D(sampler0, v_texcoord0 + vec2(-1, i)*0.004) * amount; - sum += texture2D(sampler0, v_texcoord0 + vec2(0, i)*0.004) * amount; - sum += texture2D(sampler0, v_texcoord0 + vec2(1, i)*0.004) * amount; + sum += texture2D(sampler0, v_texcoord0 + vec2(-1, i)*0.004) * u_setting.x; + sum += texture2D(sampler0, v_texcoord0 + vec2(0, i)*0.004) * u_setting.x; + sum += texture2D(sampler0, v_texcoord0 + vec2(1, i)*0.004) * u_setting.x; } if (color.r < 0.3 && color.g < 0.3 && color.b < 0.3) @@ -42,7 +41,7 @@ void main() } } - bloom = mix(color, bloom, power); + bloom = mix(color, bloom, u_setting.y); gl_FragColor.rgb = bloom; gl_FragColor.a = 1.0; } diff --git a/assets/shaders/cartoon.fsh b/assets/shaders/cartoon.fsh index 3c7be597a0..de8c0a07fd 100644 --- a/assets/shaders/cartoon.fsh +++ b/assets/shaders/cartoon.fsh @@ -10,7 +10,7 @@ precision mediump float; precision mediump int; #endif -const float bb = 0.5; // effects black border sensitivity; from 0.0 to 1.0 +uniform vec4 u_setting; uniform sampler2D sampler0; @@ -39,7 +39,7 @@ void main() float d2=dot(abs(c20-c02),dt); float hl=dot(abs(c01-c21),dt); float vl=dot(abs(c10-c12),dt); - float d = bb*(d1+d2+hl+vl)/(dot(c11,dt)+0.15); + float d = u_setting.x*(d1+d2+hl+vl)/(dot(c11,dt)+0.15); float lc = 4.0*length(c11); float f = fract(lc); f*=f; diff --git a/assets/shaders/colorcorrection.fsh b/assets/shaders/colorcorrection.fsh new file mode 100644 index 0000000000..cf62884f5a --- /dev/null +++ b/assets/shaders/colorcorrection.fsh @@ -0,0 +1,20 @@ +// Color correction + +#ifdef GL_ES +precision mediump float; +precision mediump int; +#endif + +uniform sampler2D sampler0; +varying vec2 v_texcoord0; + +uniform vec4 u_setting; + +void main() +{ + vec3 rgb = texture2D( sampler0, v_texcoord0 ).xyz; + rgb = vec3(mix(vec3(dot(rgb, vec3(0.299, 0.587, 0.114))), rgb, u_setting.y)); + rgb = (rgb-0.5)*u_setting.z+0.5+u_setting.x-1.0; + gl_FragColor.rgb = pow(rgb, vec3(1.0/u_setting.w)); + gl_FragColor.a = 1.0; +} diff --git a/assets/shaders/defaultshaders.ini b/assets/shaders/defaultshaders.ini index c690a015ac..4771bdfe09 100644 --- a/assets/shaders/defaultshaders.ini +++ b/assets/shaders/defaultshaders.ini @@ -32,10 +32,23 @@ Vertex=fxaa.vsh Name=Bloom Fragment=bloom.fsh Vertex=fxaa.vsh +SettingName1=Amount +SettingDefaultValue1=0.6 +SettingMaxValue1=1.0 +SettingMinValue1=0.0 +SettingName2=Power +SettingDefaultValue2=0.5 +SettingMaxValue2=1.0 +SettingMinValue2=0.0 [Sharpen] Name=Sharpen Fragment=sharpen.fsh Vertex=fxaa.vsh +SettingName1=Amount +SettingDefaultValue1=1.5 +SettingMaxValue1=3.0 +SettingMinValue1=1.0 +SettingStep1=0.1 [InverseColors] Name=Inverse Colors Author=Henrik @@ -46,10 +59,24 @@ Name=Scanlines (CRT) Fragment=scanlines.fsh Vertex=fxaa.vsh OutputResolution=True +SettingName1=Amount +SettingDefaultValue1=1.0 +SettingMaxValue1=1.0 +SettingMinValue1=0.0 +SettingStep1=0.1 +SettingName2=Intensity +SettingDefaultValue2=0.5 +SettingMaxValue2=1.0 +SettingMinValue2=0.0 +SettingStep2=0.05 [Cartoon] Name=Cartoon Fragment=cartoon.fsh Vertex=cartoon.vsh +SettingName1=Black border +SettingDefaultValue1=0.5 +SettingMaxValue1=1.0 +SettingMinValue1=0.0 [4xHqGLSL] Name=4xHqGLSL Upscaler Fragment=4xhqglsl.fsh @@ -129,3 +156,28 @@ Author=DeSmuME team based on Hyllian xBR work, conversion and tweaks by LunaMoo Fragment=xBRZf-DUns.fsh Vertex=5xBR.vsh OutputResolution=True +[ColorCorrection] +Name=Color correction +Fragment=colorcorrection.fsh +Vertex=fxaa.vsh +SettingName1=Brightness +SettingDefaultValue1=1.0 +SettingMaxValue1=2.0 +SettingMinValue1=0.1 +SettingStep1=0.05 +SettingName2=Saturation +SettingDefaultValue2=1.0 +SettingMaxValue2=2.0 +SettingMinValue2=0.0 +SettingStep2=0.1 +SettingName3=Constrast +SettingDefaultValue3=1.0 +SettingMaxValue3=3.0 +SettingMinValue3=0.1 +SettingStep3=0.1 +SettingName4=Gamma +SettingDefaultValue4=1.0 +SettingMaxValue4=2.0 +SettingMinValue4=0.1 +SettingStep4=0.05 + diff --git a/assets/shaders/scanlines.fsh b/assets/shaders/scanlines.fsh index 77a839a37b..ac568b14a9 100644 --- a/assets/shaders/scanlines.fsh +++ b/assets/shaders/scanlines.fsh @@ -8,13 +8,12 @@ precision mediump int; uniform sampler2D sampler0; varying vec2 v_texcoord0; -float amount = 1.0; // suitable range = 0.0 - 1.0 -float intensity = 0.5; // suitable range = 0.0 - 1.0 +uniform vec4 u_setting; void main() { - float pos0 = ((v_texcoord0.y + 1.0) * 170.0*amount); - float pos1 = cos((fract( pos0 ) - 0.5)*3.1415926*intensity)*1.5; + float pos0 = ((v_texcoord0.y + 1.0) * 170.0*u_setting.x); + float pos1 = cos((fract( pos0 ) - 0.5)*3.1415926*u_setting.y)*1.5; vec4 rgb = texture2D( sampler0, v_texcoord0 ); // slight contrast curve diff --git a/assets/shaders/sharpen.fsh b/assets/shaders/sharpen.fsh index d5ad04de54..3c528c7602 100644 --- a/assets/shaders/sharpen.fsh +++ b/assets/shaders/sharpen.fsh @@ -8,12 +8,12 @@ precision mediump int; uniform sampler2D sampler0; varying vec2 v_texcoord0; -float amount = 1.5; +uniform vec4 u_setting; void main() { vec3 color = texture2D(sampler0, v_texcoord0.xy).xyz; - color -= texture2D(sampler0, v_texcoord0.xy+0.0001).xyz*7.0*amount; - color += texture2D(sampler0, v_texcoord0.xy-0.0001).xyz*7.0*amount; + color -= texture2D(sampler0, v_texcoord0.xy+0.0001).xyz*7.0*u_setting.x; + color += texture2D(sampler0, v_texcoord0.xy-0.0001).xyz*7.0*u_setting.x; gl_FragColor.rgb = color; } diff --git a/cmake/Toolchains/raspberry.armv7.cmake b/cmake/Toolchains/raspberry.armv7.cmake index a72d23df46..b9a8b46a65 100644 --- a/cmake/Toolchains/raspberry.armv7.cmake +++ b/cmake/Toolchains/raspberry.armv7.cmake @@ -22,7 +22,7 @@ if(NOT PPSSPP_PI_MODEL4) endif() if(PPSSPP_PI_MODEL4) - set(ARCH_FLAGS "-march=armv7-a -mcpu=cortex-a72 -mtune=cortex-a72 -mfpu=neon-vfpv4 -mfloat-abi=hard") + set(ARCH_FLAGS "-march=armv7-a -mtune=cortex-a72 -mfpu=neon-vfpv4 -mfloat-abi=hard") else() set(ARCH_FLAGS "-mcpu=cortex-a7 -mfpu=neon -mfloat-abi=hard") endif() diff --git a/ext/native/file/file_util.cpp b/ext/native/file/file_util.cpp index 57fd61926e..5b1087b425 100644 --- a/ext/native/file/file_util.cpp +++ b/ext/native/file/file_util.cpp @@ -287,6 +287,7 @@ size_t getFilesInDir(const char *directory, std::vector *files, const info.isDirectory = isDirectory(info.fullName); info.exists = true; info.size = 0; + info.isWritable = false; // TODO - implement some kind of check if (!info.isDirectory) { std::string ext = getFileExtension(info.fullName); if (filter) { diff --git a/ext/native/native.vcxproj b/ext/native/native.vcxproj index 80b0f68e59..60f361b959 100644 --- a/ext/native/native.vcxproj +++ b/ext/native/native.vcxproj @@ -489,6 +489,7 @@ + diff --git a/ext/native/native.vcxproj.filters b/ext/native/native.vcxproj.filters index 82e64872aa..9c6af13184 100644 --- a/ext/native/native.vcxproj.filters +++ b/ext/native/native.vcxproj.filters @@ -350,6 +350,9 @@ ui + + util + diff --git a/ext/native/thin3d/GLQueueRunner.cpp b/ext/native/thin3d/GLQueueRunner.cpp index 2ea228fcd3..5a297aa974 100644 --- a/ext/native/thin3d/GLQueueRunner.cpp +++ b/ext/native/thin3d/GLQueueRunner.cpp @@ -161,8 +161,8 @@ void GLQueueRunner::RunInitSteps(const std::vector &steps, bool ski case GLRInitStepType::BUFFER_SUBDATA: { GLRBuffer *buffer = step.buffer_subdata.buffer; - glBindBuffer(GL_ARRAY_BUFFER, buffer->buffer_); - glBufferSubData(GL_ARRAY_BUFFER, step.buffer_subdata.offset, step.buffer_subdata.size, step.buffer_subdata.data); + glBindBuffer(buffer->target_, buffer->buffer_); + glBufferSubData(buffer->target_, step.buffer_subdata.offset, step.buffer_subdata.size, step.buffer_subdata.data); if (step.buffer_subdata.deleteData) delete[] step.buffer_subdata.data; CHECK_GL_ERROR_IF_DEBUG(); @@ -827,13 +827,16 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) { { GLenum attachments[3]; int count = 0; + bool isFBO = step.render.framebuffer != nullptr; + bool hasDepth = isFBO ? step.render.framebuffer->z_stencil_ : false; if (c.clear.clearMask & GL_COLOR_BUFFER_BIT) - attachments[count++] = GL_COLOR_ATTACHMENT0; - if (c.clear.clearMask & GL_DEPTH_BUFFER_BIT) - attachments[count++] = GL_DEPTH_ATTACHMENT; - if (c.clear.clearMask & GL_STENCIL_BUFFER_BIT) - attachments[count++] = GL_STENCIL_BUFFER_BIT; - glInvalidateFramebuffer(GL_FRAMEBUFFER, count, attachments); + attachments[count++] = isFBO ? GL_COLOR_ATTACHMENT0 : GL_COLOR; + if (hasDepth && (c.clear.clearMask & GL_DEPTH_BUFFER_BIT)) + attachments[count++] = isFBO ? GL_DEPTH_ATTACHMENT : GL_DEPTH; + if (hasDepth && (c.clear.clearMask & GL_STENCIL_BUFFER_BIT)) + attachments[count++] = isFBO ? GL_STENCIL_ATTACHMENT : GL_STENCIL; + if (glInvalidateFramebuffer != nullptr && count != 0) + glInvalidateFramebuffer(GL_FRAMEBUFFER, count, attachments); CHECK_GL_ERROR_IF_DEBUG(); break; } diff --git a/ext/native/thin3d/GLQueueRunner.h b/ext/native/thin3d/GLQueueRunner.h index 41bceedb20..813f8c020e 100644 --- a/ext/native/thin3d/GLQueueRunner.h +++ b/ext/native/thin3d/GLQueueRunner.h @@ -6,6 +6,7 @@ #include "gfx/gl_common.h" #include "thin3d/DataFormat.h" +#include "util/tiny_set.h" struct GLRViewport { float x, y, w, h, minZ, maxZ; @@ -295,9 +296,14 @@ struct GLRStep { GLRStep(GLRStepType _type) : stepType(_type) {} GLRStepType stepType; std::vector commands; + TinySet dependencies; union { struct { GLRFramebuffer *framebuffer; + GLRRenderPassAction color; + GLRRenderPassAction depth; + GLRRenderPassAction stencil; + // Note: not accurate. int numDraws; } render; struct { diff --git a/ext/native/thin3d/GLRenderManager.cpp b/ext/native/thin3d/GLRenderManager.cpp index 670021cf93..e1d8393162 100644 --- a/ext/native/thin3d/GLRenderManager.cpp +++ b/ext/native/thin3d/GLRenderManager.cpp @@ -279,6 +279,9 @@ void GLRenderManager::BindFramebufferAsRenderTarget(GLRFramebuffer *fb, GLRRende GLRStep *step = new GLRStep{ GLRStepType::RENDER }; // This is what queues up new passes, and can end previous ones. step->render.framebuffer = fb; + step->render.color = color; + step->render.depth = depth; + step->render.stencil = stencil; step->render.numDraws = 0; steps_.push_back(step); @@ -308,6 +311,12 @@ void GLRenderManager::BindFramebufferAsRenderTarget(GLRFramebuffer *fb, GLRRende } curRenderStep_ = step; + if (fb) { + if (color == GLRRenderPassAction::KEEP || depth == GLRRenderPassAction::KEEP || stencil == GLRRenderPassAction::KEEP) { + step->dependencies.insert(fb); + } + } + // Every step clears this state. gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE); } @@ -319,6 +328,7 @@ void GLRenderManager::BindFramebufferAsTexture(GLRFramebuffer *fb, int binding, data.bind_fb_texture.framebuffer = fb; data.bind_fb_texture.aspect = aspectBit; curRenderStep_->commands.push_back(data); + curRenderStep_->dependencies.insert(fb); } void GLRenderManager::CopyFramebuffer(GLRFramebuffer *src, GLRect2D srcRect, GLRFramebuffer *dst, GLOffset2D dstPos, int aspectMask) { @@ -328,6 +338,10 @@ void GLRenderManager::CopyFramebuffer(GLRFramebuffer *src, GLRect2D srcRect, GLR step->copy.src = src; step->copy.dst = dst; step->copy.aspectMask = aspectMask; + step->dependencies.insert(src); + bool fillsDst = dst && srcRect.x == 0 && srcRect.y == 0 && srcRect.w == dst->width && srcRect.h == dst->height; + if (dstPos.x != 0 || dstPos.y != 0 || !fillsDst) + step->dependencies.insert(dst); steps_.push_back(step); // Every step clears this state. @@ -342,6 +356,10 @@ void GLRenderManager::BlitFramebuffer(GLRFramebuffer *src, GLRect2D srcRect, GLR step->blit.dst = dst; step->blit.aspectMask = aspectMask; step->blit.filter = filter; + step->dependencies.insert(src); + bool fillsDst = dst && dstRect.x == 0 && dstRect.y == 0 && dstRect.w == dst->width && dstRect.h == dst->height; + if (!fillsDst) + step->dependencies.insert(dst); steps_.push_back(step); // Every step clears this state. @@ -356,6 +374,7 @@ bool GLRenderManager::CopyFramebufferToMemorySync(GLRFramebuffer *src, int aspec step->readback.srcRect = { x, y, w, h }; step->readback.aspectMask = aspectBits; step->readback.dstFormat = destFormat; + step->dependencies.insert(src); steps_.push_back(step); // Every step clears this state. diff --git a/ext/native/thin3d/VulkanQueueRunner.cpp b/ext/native/thin3d/VulkanQueueRunner.cpp index 28aa244776..a74a679e6e 100644 --- a/ext/native/thin3d/VulkanQueueRunner.cpp +++ b/ext/native/thin3d/VulkanQueueRunner.cpp @@ -1,4 +1,4 @@ -#include +#include #include "base/timeutil.h" #include "DataFormat.h" @@ -752,13 +752,27 @@ std::string VulkanQueueRunner::StepToString(const VKRStep &step) const { void VulkanQueueRunner::ApplyRenderPassMerge(std::vector &steps) { // First let's count how many times each framebuffer is rendered to. // If it's more than one, let's do our best to merge them. This can help God of War quite a bit. - std::map counts; + std::unordered_map counts; for (int i = 0; i < (int)steps.size(); i++) { if (steps[i]->stepType == VKRStepType::RENDER) { counts[steps[i]->render.framebuffer]++; } } + auto mergeRenderSteps = [](VKRStep *dst, VKRStep *src) { + // OK. Now, if it's a render, slurp up all the commands and kill the step. + // Also slurp up any pretransitions. + dst->preTransitions.insert(dst->preTransitions.end(), src->preTransitions.begin(), src->preTransitions.end()); + dst->commands.insert(dst->commands.end(), src->commands.begin(), src->commands.end()); + // So we don't consider it for other things, maybe doesn't matter. + src->dependencies.clear(); + src->stepType = VKRStepType::RENDER_SKIP; + }; + auto renderHasClear = [](const VKRStep *step) { + const auto &r = step->render; + return r.color == VKRRenderPassAction::CLEAR || r.depth == VKRRenderPassAction::CLEAR || r.stencil == VKRRenderPassAction::CLEAR; + }; + // Now, let's go through the steps. If we find one that is rendered to more than once, // we'll scan forward and slurp up any rendering that can be merged across. for (int i = 0; i < (int)steps.size(); i++) { @@ -767,39 +781,36 @@ void VulkanQueueRunner::ApplyRenderPassMerge(std::vector &steps) { TinySet touchedFramebuffers; // must be the same fast-size as the dependencies TinySet for annoying reasons. for (int j = i + 1; j < (int)steps.size(); j++) { // If any other passes are reading from this framebuffer as-is, we cancel the scan. + if (steps[j]->dependencies.contains(fb)) { + // Reading from itself means a KEEP, which is okay. + if (steps[j]->stepType != VKRStepType::RENDER || steps[j]->render.framebuffer != fb) + break; + } switch (steps[j]->stepType) { case VKRStepType::RENDER: - if (steps[j]->dependencies.contains(fb)) { - goto done_fb; - } - // Prevent Unknown's example case from https://github.com/hrydgard/ppsspp/pull/12242 - if (steps[j]->dependencies.contains(touchedFramebuffers)) { - goto done_fb; - } - if (steps[j]->render.framebuffer == fb && - steps[j]->render.color != VKRRenderPassAction::CLEAR && - steps[j]->render.depth != VKRRenderPassAction::CLEAR && - steps[j]->render.stencil != VKRRenderPassAction::CLEAR) { - // ok. Now, if it's a render, slurp up all the commands - // and kill the step. - // Also slurp up any pretransitions. - steps[i]->preTransitions.insert(steps[i]->preTransitions.end(), steps[j]->preTransitions.begin(), steps[j]->preTransitions.end()); - steps[i]->commands.insert(steps[i]->commands.end(), steps[j]->commands.begin(), steps[j]->commands.end()); - steps[j]->stepType = VKRStepType::RENDER_SKIP; - } - // Remember the framebuffer this wrote to. We can't merge with later passes that depend on these. - if (steps[j]->render.framebuffer != fb) { + if (steps[j]->render.framebuffer == fb) { + // Prevent Unknown's example case from https://github.com/hrydgard/ppsspp/pull/12242 + if (renderHasClear(steps[j]) || steps[j]->dependencies.contains(touchedFramebuffers)) { + goto done_fb; + } else { + // Safe to merge, great. + mergeRenderSteps(steps[i], steps[j]); + } + } else { + // Remember the framebuffer this wrote to. We can't merge with later passes that depend on these. touchedFramebuffers.insert(steps[j]->render.framebuffer); } break; case VKRStepType::COPY: - if (steps[j]->copy.src == fb || steps[j]->copy.dst == fb) { + if (steps[j]->copy.dst == fb) { + // Without framebuffer "renaming", we can't merge past a clobbered fb. goto done_fb; } touchedFramebuffers.insert(steps[j]->copy.dst); break; case VKRStepType::BLIT: - if (steps[j]->blit.src == fb || steps[j]->blit.dst == fb) { + if (steps[j]->blit.dst == fb) { + // Without framebuffer "renaming", we can't merge past a clobbered fb. goto done_fb; } touchedFramebuffers.insert(steps[j]->blit.dst); @@ -807,12 +818,13 @@ void VulkanQueueRunner::ApplyRenderPassMerge(std::vector &steps) { case VKRStepType::READBACK: // Not sure this has much effect, when executed READBACK is always the last step // since we stall the GPU and wait immediately after. - if (steps[j]->readback.src == fb) { - goto done_fb; - } + break; + case VKRStepType::RENDER_SKIP: + case VKRStepType::READBACK_IMAGE: break; default: - break; + // We added a new step? Might be unsafe. + goto done_fb; } } done_fb: diff --git a/ext/native/thin3d/VulkanQueueRunner.h b/ext/native/thin3d/VulkanQueueRunner.h index 129c441b86..1c9e46a5cf 100644 --- a/ext/native/thin3d/VulkanQueueRunner.h +++ b/ext/native/thin3d/VulkanQueueRunner.h @@ -6,6 +6,7 @@ #include "Common/Vulkan/VulkanContext.h" #include "math/dataconv.h" #include "thin3d/DataFormat.h" +#include "util/tiny_set.h" class VKRFramebuffer; struct VKRImage; @@ -17,70 +18,6 @@ enum { QUEUE_HACK_RENDERPASS_MERGE = 8, }; -// Insert-only small-set implementation. Performs no allocation unless MaxFastSize is exceeded. -template -struct TinySet { - ~TinySet() { delete slowLookup_; } - inline void insert(T t) { - // Fast linear scan. - for (int i = 0; i < fastCount; i++) { - if (fastLookup_[i] == t) - return; // We already have it. - } - // Fast insertion - if (fastCount < MaxFastSize) { - fastLookup_[fastCount++] = t; - return; - } - // Fall back to slow path. - insertSlow(t); - } - bool contains(T t) const { - for (int i = 0; i < fastCount; i++) { - if (fastLookup_[i] == t) - return true; - } - if (slowLookup_) { - for (auto x : *slowLookup_) { - if (x == t) - return true; - } - } - return false; - } - bool contains(const TinySet &otherSet) { - // Awkward, kind of ruins the fun. - for (int i = 0; i < fastCount; i++) { - if (otherSet.contains(fastLookup_[i])) - return true; - } - if (slowLookup_) { - for (auto x : *slowLookup_) { - if (otherSet.contains(x)) - return true; - } - } - return false; - } - -private: - void insertSlow(T t) { - if (!slowLookup_) { - slowLookup_ = new std::vector(); - } else { - for (size_t i = 0; i < slowLookup_->size(); i++) { - if ((*slowLookup_)[i] == t) - return; - } - } - slowLookup_->push_back(t); - } - T fastLookup_[MaxFastSize]; - int fastCount = 0; - int slowCount = 0; - std::vector *slowLookup_ = nullptr; -}; - enum class VKRRenderCommand : uint8_t { REMOVED, BIND_PIPELINE, diff --git a/ext/native/thin3d/VulkanRenderManager.cpp b/ext/native/thin3d/VulkanRenderManager.cpp index 7f0774e698..475b64aa06 100644 --- a/ext/native/thin3d/VulkanRenderManager.cpp +++ b/ext/native/thin3d/VulkanRenderManager.cpp @@ -493,6 +493,13 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR step->render.finalColorLayout = !fb ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED; steps_.push_back(step); + if (fb) { + // If there's a KEEP, we naturally read from the framebuffer. + if (color == VKRRenderPassAction::KEEP || depth == VKRRenderPassAction::KEEP || stencil == VKRRenderPassAction::KEEP) { + step->dependencies.insert(fb); + } + } + curRenderStep_ = step; if (fb) { curWidth_ = fb->width; @@ -516,6 +523,7 @@ bool VulkanRenderManager::CopyFramebufferToMemorySync(VKRFramebuffer *src, int a step->readback.src = src; step->readback.srcRect.offset = { x, y }; step->readback.srcRect.extent = { (uint32_t)w, (uint32_t)h }; + step->dependencies.insert(src); steps_.push_back(step); curRenderStep_ = nullptr; @@ -824,6 +832,10 @@ void VulkanRenderManager::CopyFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, step->copy.srcRect = srcRect; step->copy.dst = dst; step->copy.dstPos = dstPos; + step->dependencies.insert(src); + bool fillsDst = dst && srcRect.offset.x == 0 && srcRect.offset.y == 0 && srcRect.extent.width == dst->width && srcRect.extent.height == dst->height; + if (dstPos.x != 0 || dstPos.y != 0 || !fillsDst) + step->dependencies.insert(dst); std::unique_lock lock(mutex_); steps_.push_back(step); @@ -862,6 +874,10 @@ void VulkanRenderManager::BlitFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, step->blit.dst = dst; step->blit.dstRect = dstRect; step->blit.filter = filter; + step->dependencies.insert(src); + bool fillsDst = dst && dstRect.offset.x == 0 && dstRect.offset.y == 0 && dstRect.extent.width == dst->width && dstRect.extent.height == dst->height; + if (!fillsDst) + step->dependencies.insert(dst); std::unique_lock lock(mutex_); steps_.push_back(step); diff --git a/ext/native/util/tiny_set.h b/ext/native/util/tiny_set.h new file mode 100644 index 0000000000..8a7cf89fa6 --- /dev/null +++ b/ext/native/util/tiny_set.h @@ -0,0 +1,71 @@ +#pragma once + +#include + +// Insert-only small-set implementation. Performs no allocation unless MaxFastSize is exceeded. +template +struct TinySet { + ~TinySet() { delete slowLookup_; } + inline void insert(T t) { + // Fast linear scan. + for (int i = 0; i < fastCount; i++) { + if (fastLookup_[i] == t) + return; // We already have it. + } + // Fast insertion + if (fastCount < MaxFastSize) { + fastLookup_[fastCount++] = t; + return; + } + // Fall back to slow path. + insertSlow(t); + } + bool contains(T t) const { + for (int i = 0; i < fastCount; i++) { + if (fastLookup_[i] == t) + return true; + } + if (slowLookup_) { + for (auto x : *slowLookup_) { + if (x == t) + return true; + } + } + return false; + } + bool contains(const TinySet &otherSet) { + // Awkward, kind of ruins the fun. + for (int i = 0; i < fastCount; i++) { + if (otherSet.contains(fastLookup_[i])) + return true; + } + if (slowLookup_) { + for (auto x : *slowLookup_) { + if (otherSet.contains(x)) + return true; + } + } + return false; + } + void clear() { + delete slowLookup_; + slowLookup_ = nullptr; + fastCount = 0; + } + +private: + void insertSlow(T t) { + if (!slowLookup_) { + slowLookup_ = new std::vector(); + } else { + for (size_t i = 0; i < slowLookup_->size(); i++) { + if ((*slowLookup_)[i] == t) + return; + } + } + slowLookup_->push_back(t); + } + T fastLookup_[MaxFastSize]; + int fastCount = 0; + std::vector *slowLookup_ = nullptr; +}; diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 790fb463b7..d59b2a82f3 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -430,7 +430,7 @@ int main(int argc, const char* argv[]) #endif if (stateToLoad != NULL) - SaveState::Load(stateToLoad); + SaveState::Load(stateToLoad, -1); std::vector failedTests; std::vector passedTests; diff --git a/libretro/libretro.cpp b/libretro/libretro.cpp index 4df14d381a..9f29455794 100644 --- a/libretro/libretro.cpp +++ b/libretro/libretro.cpp @@ -317,7 +317,6 @@ void retro_init(void) { g_Config.iGlobalVolume = VOLUME_MAX - 1; g_Config.iAltSpeedVolume = -1; g_Config.bEnableSound = true; - g_Config.bAudioResampler = false; g_Config.iCwCheatRefreshRate = 60; g_Config.iFirmwareVersion = PSP_DEFAULT_FIRMWARE; diff --git a/unittest/TestArm64Emitter.cpp b/unittest/TestArm64Emitter.cpp index 2efeb77847..af0f37ca45 100644 --- a/unittest/TestArm64Emitter.cpp +++ b/unittest/TestArm64Emitter.cpp @@ -38,7 +38,7 @@ bool TestArm64Emitter() { u32 code[512]; - ARM64XEmitter emitter((u8 *)code); + ARM64XEmitter emitter((u8 *)code, (u8 *)code); ARM64FloatEmitter fp(&emitter); emitter.MOVfromSP(X3);