Compare commits

...
21 Commits
Author SHA1 Message Date
SternXDandTy f5310f0ca4 Achievements: Group leaderboards by subset and tracking state 2026-06-13 15:35:03 -04:00
KamFretoZandTy 972fcfa0f0 Qt: Fix Formatting 2026-06-13 15:34:46 -04:00
KamFretoZandTy b0582f6ea3 Qt/OSD: Fix GPU stats not updating when Show GPU Usage settings arent enabled 2026-06-13 15:34:46 -04:00
KamFretoZandTy 8b6c28b374 Qt/Shortcut: Use Nearest-Neighbor scaling for icon preview
No need to get fancy
2026-06-13 15:34:46 -04:00
KamFretoZandTy 0a3c27765d Achievements: Simplify sound effects playback 2026-06-13 15:34:46 -04:00
KamFretoZandTy 040a8b8fc6 Qt: Fix Status Bar visibility when Languange Changes 2026-06-13 15:34:46 -04:00
KamFretoZandTy 9f67f72d3c Qt: Revamp the status bar 2026-06-13 15:34:46 -04:00
jasaavedandTy 04438ace57 FullscreenUI: Fix analog requiring neutral position to reset
Before, the analog stick would have to reset to the neutral position before allowing the cursor to move again. This has been fixed, cursor moves now once it crosses 45 degrees towards a new axis.
2026-06-13 15:34:27 -04:00
jasaavedandTy e16236e450 FullscreenUI: Cleanup formatting
Fix formatting in pre-existing code
2026-06-13 15:34:27 -04:00
jasaavedandTy 3807517e02 FullscreenUI: Fix analog/dpad nav state shared across controllers
Per-controller state was stored in function-local statics, meaning one controller could corrupt the input state of another. Move to a per-controller map keyed by controller ID.
2026-06-13 15:34:27 -04:00
jasaavedandTy ab8bd8e754 FullscreenUI: Add analog navigation for Xinput and Dinput 2026-06-13 15:34:27 -04:00
jasaavedandTy 65d63085f8 FullscreenUI: Fix analog stick input bursting
Analog inputs are now read as single event once it passes the deadzone / threshold.
2026-06-13 15:34:27 -04:00
jasaavedandTy 2c8ecc3ea7 FullscreenUI: Remove diagonal navigation
Removing diagonal navigation makes moving across titles feel smoother and works closer to Steam's big picture mode.
2026-06-13 15:34:27 -04:00
jasaavedandTy 5fbc79c466 FullscreenUI: Add analog control 2026-06-13 15:34:27 -04:00
SternXDandTy 8fe4183856 FullscreenUI: Fix volume sliders to match Qts max 200%
Signed-off-by: SternXD <stern@sidestore.io>
2026-06-13 15:33:29 -04:00
SternXDandTy 0d52608f1d FullscreenUI: Add some missing settings from Qt
Signed-off-by: SternXD <stern@sidestore.io>
2026-06-13 15:33:29 -04:00
SternXDandTy 5df3c54b84 FullscreenUI: Move Achievement page/login to settings file
I guess this was an oversight when I was splitting up FullscreenUI

Signed-off-by: SternXD <stern@sidestore.io>
2026-06-13 15:33:29 -04:00
SternXDandTy b2db2e71b6 FullscreenUI: Correct default label for Division Rounding Mode
Signed-off-by: SternXD <stern@sidestore.io>
2026-06-13 15:33:29 -04:00
SternXDandTy 7978370b39 Common/Emitter: Inline _xMovRtoR into xImpl_Mov operator
Signed-off-by: SternXD <stern@sidestore.io>
2026-06-13 09:34:53 -04:00
TheLastRarandTy d187c61599 GS/DX12: Enable tight alignment 2026-06-12 18:08:03 -04:00
TheLastRarandTy fa745a58b2 3rdparty: Update D3D12MA to v3.2.0 2026-06-12 18:08:03 -04:00
48 changed files with 1876 additions and 888 deletions
@@ -97,6 +97,7 @@ set SHADERC_SPIRVHEADERS=58006c901d1d5c37dece6b6610e9af87fa951375
set SHADERC_SPIRVTOOLS=6337eb62cadd7d124ac6789bf39c0f71148f0a73
set AGILITYSDK=1.619.2
set DXHEADERS=1.619.1
call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" 3529cc37297a5a7aae4486843b9fd41c30df1d79a770f85e240b537dcc327ca5 || goto error
call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" 37fba768f2780580dfae535ad6654cb9dc0bf2272e71b9b9781988de9ed0dac0 || goto error
@@ -129,6 +130,7 @@ call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/
call :downloadfile "plutovg-%PLUTOVG%.zip" "https://github.com/sammycage/plutovg/archive/v%PLUTOVG%.zip" 4fe4e48f28aa80171b2166d45c0976ab0f21eecedb52cd4c3ef73b5afb48fac9 || goto error
call :downloadfile "plutosvg-%PLUTOSVG%.zip" "https://github.com/sammycage/plutosvg/archive/v%PLUTOSVG%.zip" 82dee2c57ad712bdd6d6d81d3e76249d89caa4b5a4214353660fd5adff12201a || goto error
call :downloadfile "agility-sdk-%AGILITYSDK%.nupkg" "https://www.nuget.org/api/v2/package/Microsoft.Direct3D.D3D12/%AGILITYSDK%" eb92d90bb23b2ec23410c41d791e41dbdbec942ab946924d1fdcb31eac6f0735 || goto error
call :downloadfile "DirectX-Headers-%DXHEADERS%.zip" "https://github.com/microsoft/DirectX-Headers/archive/v%DXHEADERS%.zip" 9eb8b102a90a42e4ea72a825f7d249d55ec90d164f030966c9b7784b93374927 || goto error
call :downloadfile "rapidyaml-%RAPIDYAML%-src.zip" "https://github.com/biojppm/rapidyaml/releases/download/v%RAPIDYAML%/rapidyaml-%RAPIDYAML%-src.zip" 96276f55b9fa7837ac8f3f72fd52965879cbb5d5d2e6af548c69a177fb078304 || goto error
call :downloadfile "shaderc-%SHADERC%.zip" "https://github.com/google/shaderc/archive/refs/tags/v%SHADERC%.zip" f9401cc5cb36c276cd1e072b6595dbd728148e8dba389e50f7339e2d388dbc08 || goto error
@@ -507,6 +509,16 @@ if %DEBUG%==1 (
)
cd .. || goto error
rem DirectX Headers include a CMakeList file, which is absent in the Nuget package
echo Unpacking DirectX Headers
rmdir /S /Q "DirectX-Headers-%DXHEADERS%"
%SEVENZIP% x "DirectX-Headers-%DXHEADERS%.zip" || goto error
cd "DirectX-Headers-%DXHEADERS%" || goto error
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="%INSTALLDIR%" -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DDXHEADERS_BUILD_TEST=OFF -DDXHEADERS_BUILD_GOOGLE_TEST=OFF -B build -G Ninja || goto error
cmake --build build --parallel || goto error
ninja -C build install || goto error
cd .. || goto error
echo Building shaderc...
rmdir /S /Q "shaderc-%SHADERC%"
%SEVENZIP% x "shaderc-%SHADERC%.zip" || goto error
+2
View File
@@ -3,5 +3,7 @@ add_library(D3D12MemAlloc
src/D3D12MemAlloc.cpp
)
target_link_libraries(D3D12MemAlloc PRIVATE Microsoft::DirectX-Headers)
target_include_directories(D3D12MemAlloc PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
disable_compiler_warnings_for_target(D3D12MemAlloc)
+3 -2
View File
@@ -20,6 +20,7 @@
<ImportGroup Label="PropertySheets">
<Import Project="..\DefaultProjectRootDir.props" />
<Import Project="..\3rdparty.props" />
<Import Project="..\..\common\vsprops\DepsDir.props" />
<Import Condition="$(Configuration.Contains(Debug))" Project="..\..\common\vsprops\CodeGen_Debug.props" />
<Import Condition="$(Configuration.Contains(Devel))" Project="..\..\common\vsprops\CodeGen_Devel.props" />
<Import Condition="$(Configuration.Contains(Release))" Project="..\..\common\vsprops\CodeGen_Release.props" />
@@ -37,9 +38,9 @@
</ItemGroup>
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>D3D12MA_USING_DIRECTX_HEADERS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
<AdditionalIncludeDirectories>$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(DepsIncludeDir);$(ProjectDir)include;$(ProjectDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+12 -3
View File
@@ -24,7 +24,7 @@
/** \mainpage D3D12 Memory Allocator
<b>Version 3.1.0</b> (2026-02-23)
<b>Version 3.2.0</b> (2026-06-05)
Copyright (c) 2019-2026 Advanced Micro Devices, Inc. All rights reserved. \n
License: MIT
@@ -889,10 +889,17 @@ enum POOL_FLAGS
*/
POOL_FLAG_MSAA_TEXTURES_ALWAYS_COMMITTED = 0x2,
/** Every allocation made in this pool will be created as a committed resource - will have its own memory block.
There is also an equivalent flag for the entire allocator: D3D12MA::ALLOCATOR_FLAG_ALWAYS_COMMITTED.
*/
POOL_FLAG_ALWAYS_COMMITTED = 0x4,
/** Disables tight resource alignment for all resources created from this pool.
When tight alignment is supported and enabled for the allocator, resources created from custom pools
normally use it automatically. Specify this flag to restore pre-tight-alignment behavior for this pool:
tight alignment won't be requested and small buffers may again be preferred as committed resources.
*/
POOL_FLAG_DONT_USE_TIGHT_ALIGNMENT = 0x8,
// Bit mask to extract only `ALGORITHM` bits from entire set of flags.
POOL_FLAG_ALGORITHM_MASK = POOL_FLAG_ALGORITHM_LINEAR
@@ -1114,7 +1121,8 @@ enum ALLOCATOR_FLAGS
It can also be disabled for a single allocation by using #ALLOCATION_FLAG_STRATEGY_MIN_TIME.
If the tight resource alignment feature is used by the library (which happens automatically whenever supported,
unless you use flag #ALLOCATOR_FLAG_DONT_USE_TIGHT_ALIGNMENT), then small buffers are not preferred as committed.
unless you use flag #ALLOCATOR_FLAG_DONT_USE_TIGHT_ALIGNMENT or
#POOL_FLAG_DONT_USE_TIGHT_ALIGNMENT on a custom pool), then small buffers are not preferred as committed.
Long story short, you don't need to specify any of these flags.
The library chooses the most optimal method automatically.
*/
@@ -2892,6 +2900,7 @@ as it is no longer needed.
You can check if the tight alignment it is available in the current system by calling D3D12MA::Allocator::IsTightAlignmentSupported().
You can tell the library to not use it by specifying D3D12MA::ALLOCATOR_FLAG_DONT_USE_TIGHT_ALIGNMENT.
Custom pools can opt out independently by specifying D3D12MA::POOL_FLAG_DONT_USE_TIGHT_ALIGNMENT.
Typically, you don't need to do any of those.
The library automatically aligns all buffers to at least 256 B, even when the system supports smaller alignment.
+249 -57
View File
@@ -163,6 +163,14 @@ especially to test compatibility with D3D12_RESOURCE_HEAP_TIER_1 on modern GPUs.
#endif
#endif
#ifndef D3D12MA_OPTIONS4_SUPPORTED
#ifdef __ID3D12Device5_INTERFACE_DEFINED__
#define D3D12MA_OPTIONS4_SUPPORTED 1
#else
#define D3D12MA_OPTIONS4_SUPPORTED 0
#endif
#endif
#ifndef D3D12MA_DEBUG_LOG
#define D3D12MA_DEBUG_LOG(format, ...)
/*
@@ -703,6 +711,13 @@ static UINT GetBitsPerPixel(DXGI_FORMAT format)
case DXGI_FORMAT_R24_UNORM_X8_TYPELESS:
case DXGI_FORMAT_X24_TYPELESS_G8_UINT:
return 32;
case DXGI_FORMAT_B8G8R8A8_UNORM:
case DXGI_FORMAT_B8G8R8X8_UNORM:
case DXGI_FORMAT_B8G8R8A8_TYPELESS:
case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
case DXGI_FORMAT_B8G8R8X8_TYPELESS:
case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
return 32;
case DXGI_FORMAT_R8G8_TYPELESS:
case DXGI_FORMAT_R8G8_UNORM:
case DXGI_FORMAT_R8G8_UINT:
@@ -717,6 +732,9 @@ static UINT GetBitsPerPixel(DXGI_FORMAT format)
case DXGI_FORMAT_R16_SNORM:
case DXGI_FORMAT_R16_SINT:
return 16;
case DXGI_FORMAT_B5G6R5_UNORM:
case DXGI_FORMAT_B5G5R5A1_UNORM:
return 16;
case DXGI_FORMAT_R8_TYPELESS:
case DXGI_FORMAT_R8_UNORM:
case DXGI_FORMAT_R8_UINT:
@@ -767,46 +785,175 @@ static ResourceClass ResourceDescToResourceClass(const D3D12_RESOURCE_DESC_T& re
(resDesc.Flags & (D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL)) != 0;
return isRenderTargetOrDepthStencil ? ResourceClass::RT_DS_Texture : ResourceClass::Non_RT_DS_Texture;
}
static bool ResourceDimensionIsTexture(D3D12_RESOURCE_DIMENSION dimension)
{
return dimension == D3D12_RESOURCE_DIMENSION_TEXTURE1D ||
dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D ||
dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D;
}
static bool ResourceFlagsContainRenderTargetOrDepthStencil(D3D12_RESOURCE_FLAGS flags)
{
return (flags & (D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL)) != 0;
}
// This algorithm is overly conservative.
struct SmallAlignmentTileShape
{
UINT Width;
UINT Height;
UINT Depth;
};
static bool GetSmallAlignmentTileShape(
D3D12_RESOURCE_DIMENSION dimension,
bool isMsaa,
UINT bitsPerUnit,
SmallAlignmentTileShape& outTileShape)
{
if (isMsaa)
{
if (dimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D)
return false;
switch (bitsPerUnit)
{
case 8: outTileShape = { 256, 256, 1 }; return true;
case 16: outTileShape = { 256, 128, 1 }; return true;
case 32: outTileShape = { 128, 128, 1 }; return true;
case 64: outTileShape = { 128, 64, 1 }; return true;
case 128: outTileShape = { 64, 64, 1 }; return true;
case 256: outTileShape = { 64, 32, 1 }; return true;
case 512: outTileShape = { 32, 32, 1 }; return true;
case 1024: outTileShape = { 32, 16, 1 }; return true;
case 2048: outTileShape = { 16, 16, 1 }; return true;
default: return false;
}
}
switch (dimension)
{
case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
switch (bitsPerUnit)
{
case 8: outTileShape = { 4096, 1, 1 }; return true;
case 16: outTileShape = { 2048, 1, 1 }; return true;
case 32: outTileShape = { 1024, 1, 1 }; return true;
case 64: outTileShape = { 512, 1, 1 }; return true;
case 128: outTileShape = { 256, 1, 1 }; return true;
default: return false;
}
case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
switch (bitsPerUnit)
{
case 8: outTileShape = { 64, 64, 1 }; return true;
case 16: outTileShape = { 64, 32, 1 }; return true;
case 32: outTileShape = { 32, 32, 1 }; return true;
case 64: outTileShape = { 32, 16, 1 }; return true;
case 128: outTileShape = { 16, 16, 1 }; return true;
default: return false;
}
case D3D12_RESOURCE_DIMENSION_TEXTURE3D:
// 4 KB counterparts of the documented 64 KB volume-tile shapes.
switch (bitsPerUnit)
{
case 8: outTileShape = { 16, 16, 16 }; return true;
case 16: outTileShape = { 16, 16, 8 }; return true;
case 32: outTileShape = { 16, 8, 8 }; return true;
case 64: outTileShape = { 8, 8, 8 }; return true;
case 128: outTileShape = { 8, 8, 4 }; return true;
default: return false;
}
default:
return false;
}
}
// This algorithm is overly conservative and applies only to the legacy small-alignment heuristic path.
template<typename D3D12_RESOURCE_DESC_T>
static bool CanUseSmallAlignment(const D3D12_RESOURCE_DESC_T& resourceDesc)
{
if (resourceDesc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D)
if (resourceDesc.SampleDesc.Count == 0 ||
resourceDesc.Layout != D3D12_TEXTURE_LAYOUT_UNKNOWN ||
resourceDesc.Width > UINT_MAX)
{
return false;
if ((resourceDesc.Flags & (D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL)) != 0)
}
const bool isMsaa = resourceDesc.SampleDesc.Count > 1;
const bool isRtDs = ResourceFlagsContainRenderTargetOrDepthStencil(resourceDesc.Flags);
if (isMsaa && !isRtDs)
{
return false;
if (resourceDesc.SampleDesc.Count > 1)
return false;
if (resourceDesc.DepthOrArraySize != 1)
}
if (!isMsaa &&
isRtDs)
{
return false;
}
UINT sizeX = (UINT)resourceDesc.Width;
UINT sizeY = resourceDesc.Height;
UINT bitsPerPixel = GetBitsPerPixel(resourceDesc.Format);
if (bitsPerPixel == 0)
UINT sizeY = 1;
UINT sizeZ = 1;
UINT bitsPerUnit = GetBitsPerPixel(resourceDesc.Format);
if (bitsPerUnit == 0)
return false;
if (IsFormatCompressed(resourceDesc.Format))
switch (resourceDesc.Dimension)
{
sizeX = DivideRoundingUp(sizeX, 4u);
sizeY = DivideRoundingUp(sizeY, 4u);
bitsPerPixel *= 16;
case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
if (isMsaa)
return false;
// For 1D arrays, small-alignment eligibility depends on the mip0 slice size, not array size.
if (IsFormatCompressed(resourceDesc.Format))
return false;
break;
case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
// For 2D arrays, small-alignment eligibility depends on mip0 width/height, not array size.
sizeY = resourceDesc.Height;
if (isMsaa && IsFormatCompressed(resourceDesc.Format))
return false;
if (IsFormatCompressed(resourceDesc.Format))
{
sizeX = DivideRoundingUp(sizeX, 4u);
sizeY = DivideRoundingUp(sizeY, 4u);
bitsPerUnit *= 16;
}
break;
case D3D12_RESOURCE_DIMENSION_TEXTURE3D:
if (isMsaa)
return false;
sizeY = resourceDesc.Height;
sizeZ = resourceDesc.DepthOrArraySize;
if (IsFormatCompressed(resourceDesc.Format))
{
sizeX = DivideRoundingUp(sizeX, 4u);
sizeY = DivideRoundingUp(sizeY, 4u);
bitsPerUnit *= 16;
}
break;
default:
return false;
}
UINT tileSizeX = 0, tileSizeY = 0;
switch (bitsPerPixel)
{
case 8: tileSizeX = 64; tileSizeY = 64; break;
case 16: tileSizeX = 64; tileSizeY = 32; break;
case 32: tileSizeX = 32; tileSizeY = 32; break;
case 64: tileSizeX = 32; tileSizeY = 16; break;
case 128: tileSizeX = 16; tileSizeY = 16; break;
default: return false;
}
bitsPerUnit *= resourceDesc.SampleDesc.Count;
const UINT tileCount = DivideRoundingUp(sizeX, tileSizeX) * DivideRoundingUp(sizeY, tileSizeY);
return tileCount <= 16;
SmallAlignmentTileShape tileShape = {};
if (!GetSmallAlignmentTileShape(resourceDesc.Dimension, isMsaa, bitsPerUnit, tileShape))
return false;
const UINT64 tileCount =
UINT64(DivideRoundingUp(sizeX, tileShape.Width)) *
UINT64(DivideRoundingUp(sizeY, tileShape.Height)) *
UINT64(DivideRoundingUp(sizeZ, tileShape.Depth));
// Non-MSAA compares 64 KB against 4 KB tiles => 16 pages. MSAA compares 4 MB against 64 KB tiles => 64 pages.
return tileCount <= (isMsaa ? 64 : 16);
}
static bool ValidateAllocateMemoryParameters(
@@ -5988,6 +6135,7 @@ public:
BOOL IsCacheCoherentUMA() const { return m_D3D12Architecture.CacheCoherentUMA; }
bool SupportsResourceHeapTier2() const { return m_D3D12Options.ResourceHeapTier >= D3D12_RESOURCE_HEAP_TIER_2; }
bool IsGPUUploadHeapSupported() const { return m_GPUUploadHeapSupported != FALSE; }
bool IsMsaa64KBAlignedTextureSupported() const { return m_MSAA64KBAlignedTextureSupported != FALSE; }
bool IsTightAlignmentSupported() const { return m_TightAlignmentSupported != FALSE; }
bool IsTightAlignmentEnabled() const { return IsTightAlignmentSupported() && m_UseTightAlignment; }
bool UseMutex() const { return m_UseMutex; }
@@ -6076,7 +6224,7 @@ private:
const bool m_UseMutex;
const bool m_AlwaysCommitted;
const bool m_MsaaAlwaysCommitted;
bool m_PreferSmallBuffersCommitted;
const bool m_PreferSmallBuffersCommitted;
const bool m_UseTightAlignment;
bool m_DefaultPoolsNotZeroed = false;
ID3D12Device* m_Device; // AddRef
@@ -6105,6 +6253,7 @@ private:
DXGI_ADAPTER_DESC m_AdapterDesc;
D3D12_FEATURE_DATA_D3D12_OPTIONS m_D3D12Options;
BOOL m_GPUUploadHeapSupported = FALSE;
BOOL m_MSAA64KBAlignedTextureSupported = FALSE;
BOOL m_TightAlignmentSupported = FALSE;
D3D12_FEATURE_DATA_ARCHITECTURE m_D3D12Architecture;
AllocationObjectAllocator m_AllocationObjectAllocator;
@@ -6121,7 +6270,7 @@ private:
*/
template<typename D3D12_RESOURCE_DESC_T>
bool PrefersCommittedAllocation(const D3D12_RESOURCE_DESC_T& resourceDesc,
ALLOCATION_FLAGS strategy);
ALLOCATION_FLAGS strategy, bool useTightAlignment) const;
// Allocates and registers new committed resource with implicit heap, as dedicated allocation.
// Creates and returns Allocation object and optionally D3D12 resource.
@@ -6141,6 +6290,7 @@ private:
template<typename D3D12_RESOURCE_DESC_T>
HRESULT CalcAllocationParams(const ALLOCATION_DESC& allocDesc, UINT64 allocSize,
const D3D12_RESOURCE_DESC_T* resDesc, // Optional
bool useTightAlignment,
BlockVector*& outBlockVector, CommittedAllocationParameters& outCommittedAllocationParams, bool& outPreferCommitted);
// Returns UINT32_MAX if index cannot be calculcated.
@@ -6174,7 +6324,10 @@ private:
template<typename D3D12_RESOURCE_DESC_T>
HRESULT GetResourceAllocationInfo(D3D12_RESOURCE_DESC_T& inOutResourceDesc,
UINT32 NumCastableFormats, const DXGI_FORMAT* pCastableFormats,
D3D12_RESOURCE_ALLOCATION_INFO& outAllocInfo) const;
D3D12_RESOURCE_ALLOCATION_INFO& outAllocInfo,
bool useTightAlignment) const;
bool IsTightAlignmentEnabled(const ALLOCATION_DESC& allocDesc) const;
bool NewAllocationWithinBudget(D3D12_HEAP_TYPE heapType, UINT64 size);
@@ -6277,6 +6430,17 @@ HRESULT AllocatorPimpl::Init(const ALLOCATOR_DESC& desc)
}
#endif // #if D3D12MA_OPTIONS16_SUPPORTED
#if D3D12MA_OPTIONS4_SUPPORTED
{
D3D12_FEATURE_DATA_D3D12_OPTIONS4 options4 = {};
hr = m_Device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS4, &options4, sizeof(options4));
if (SUCCEEDED(hr))
{
m_MSAA64KBAlignedTextureSupported = options4.MSAA64KBAlignedTextureSupported;
}
}
#endif // #if D3D12MA_OPTIONS4_SUPPORTED
#if D3D12MA_TIGHT_ALIGNMENT_SUPPORTED
{
D3D12_FEATURE_DATA_TIGHT_ALIGNMENT tightAlignment = {};
@@ -6285,13 +6449,6 @@ HRESULT AllocatorPimpl::Init(const ALLOCATOR_DESC& desc)
{
m_TightAlignmentSupported = tightAlignment.SupportTier >= D3D12_TIGHT_ALIGNMENT_TIER_1;
// If tight alignment is supported (checked by the code above) and wasn't disabled by the developer
// (with ALLOCATOR_FLAG_DONT_USE_TIGHT_ALIGNMENT), disable the preference for creating small buffers as committed,
// as if ALLOCATOR_FLAG_DONT_PREFER_SMALL_BUFFERS_COMMITTED was specified.
if (IsTightAlignmentEnabled())
{
m_PreferSmallBuffersCommitted = false;
}
}
}
#endif // #if D3D12MA_TIGHT_ALIGNMENT_SUPPORTED
@@ -6490,6 +6647,7 @@ HRESULT AllocatorPimpl::CreateResource(
}
HRESULT hr = E_NOINTERFACE;
const bool useTightAlignment = IsTightAlignmentEnabled(*pAllocDesc);
CREATE_RESOURCE_PARAMS finalCreateParams = createParams;
D3D12_RESOURCE_DESC finalResourceDesc;
#ifdef __ID3D12Device8_INTERFACE_DEFINED__
@@ -6500,7 +6658,7 @@ HRESULT AllocatorPimpl::CreateResource(
{
finalResourceDesc = *createParams.GetResourceDesc();
finalCreateParams.AccessResourceDesc() = &finalResourceDesc;
hr = GetResourceAllocationInfo(finalResourceDesc, 0, NULL, resAllocInfo);
hr = GetResourceAllocationInfo(finalResourceDesc, 0, NULL, resAllocInfo, useTightAlignment);
}
#ifdef __ID3D12Device8_INTERFACE_DEFINED__
else if (createParams.Variant == CREATE_RESOURCE_PARAMS::VARIANT_WITH_STATE_AND_DESC1)
@@ -6509,7 +6667,7 @@ HRESULT AllocatorPimpl::CreateResource(
{
finalResourceDesc1 = *createParams.GetResourceDesc1();
finalCreateParams.AccessResourceDesc1() = &finalResourceDesc1;
hr = GetResourceAllocationInfo(finalResourceDesc1, 0, NULL, resAllocInfo);
hr = GetResourceAllocationInfo(finalResourceDesc1, 0, NULL, resAllocInfo, useTightAlignment);
}
}
#endif
@@ -6521,7 +6679,7 @@ HRESULT AllocatorPimpl::CreateResource(
finalResourceDesc1 = *createParams.GetResourceDesc1();
finalCreateParams.AccessResourceDesc1() = &finalResourceDesc1;
hr = GetResourceAllocationInfo(finalResourceDesc1,
createParams.GetNumCastableFormats(), createParams.GetCastableFormats(), resAllocInfo);
createParams.GetNumCastableFormats(), createParams.GetCastableFormats(), resAllocInfo, useTightAlignment);
}
}
#endif
@@ -6547,14 +6705,14 @@ HRESULT AllocatorPimpl::CreateResource(
if (createParams.Variant >= CREATE_RESOURCE_PARAMS::VARIANT_WITH_STATE_AND_DESC1)
{
hr = CalcAllocationParams<D3D12_RESOURCE_DESC1>(*pAllocDesc, resAllocInfo.SizeInBytes,
createParams.GetResourceDesc1(),
createParams.GetResourceDesc1(), useTightAlignment,
blockVector, committedAllocationParams, preferCommitted);
}
else
#endif
{
hr = CalcAllocationParams<D3D12_RESOURCE_DESC>(*pAllocDesc, resAllocInfo.SizeInBytes,
createParams.GetResourceDesc(),
createParams.GetResourceDesc(), useTightAlignment,
blockVector, committedAllocationParams, preferCommitted);
}
if (FAILED(hr))
@@ -6601,6 +6759,7 @@ HRESULT AllocatorPimpl::AllocateMemory(
bool preferCommitted = false;
HRESULT hr = CalcAllocationParams<D3D12_RESOURCE_DESC>(*pAllocDesc, pAllocInfo->SizeInBytes,
NULL, // pResDesc
false, // useTightAlignment - irrelevant for AllocateMemory
blockVector, committedAllocationParams, preferCommitted);
if (FAILED(hr))
return hr;
@@ -6649,7 +6808,7 @@ HRESULT AllocatorPimpl::CreateAliasingResource(
{
finalResourceDesc = *createParams.GetResourceDesc();
finalCreateParams.AccessResourceDesc() = &finalResourceDesc;
hr = GetResourceAllocationInfo(finalResourceDesc, 0, NULL, resAllocInfo);
hr = GetResourceAllocationInfo(finalResourceDesc, 0, NULL, resAllocInfo, IsTightAlignmentEnabled());
}
#ifdef __ID3D12Device8_INTERFACE_DEFINED__
else if (createParams.Variant == CREATE_RESOURCE_PARAMS::VARIANT_WITH_STATE_AND_DESC1)
@@ -6658,7 +6817,7 @@ HRESULT AllocatorPimpl::CreateAliasingResource(
{
finalResourceDesc1 = *createParams.GetResourceDesc1();
finalCreateParams.AccessResourceDesc1() = &finalResourceDesc1;
hr = GetResourceAllocationInfo(finalResourceDesc1, 0, NULL, resAllocInfo);
hr = GetResourceAllocationInfo(finalResourceDesc1, 0, NULL, resAllocInfo, IsTightAlignmentEnabled());
}
}
#endif
@@ -6670,7 +6829,7 @@ HRESULT AllocatorPimpl::CreateAliasingResource(
finalResourceDesc1 = *createParams.GetResourceDesc1();
finalCreateParams.AccessResourceDesc1() = &finalResourceDesc1;
hr = GetResourceAllocationInfo(finalResourceDesc1,
createParams.GetNumCastableFormats(), createParams.GetCastableFormats(), resAllocInfo);
createParams.GetNumCastableFormats(), createParams.GetCastableFormats(), resAllocInfo, IsTightAlignmentEnabled());
}
}
#endif
@@ -7305,13 +7464,14 @@ void AllocatorPimpl::FreeStatsString(WCHAR* pStatsString)
template<typename D3D12_RESOURCE_DESC_T>
bool AllocatorPimpl::PrefersCommittedAllocation(const D3D12_RESOURCE_DESC_T& resourceDesc,
ALLOCATION_FLAGS strategy)
ALLOCATION_FLAGS strategy, bool useTightAlignment) const
{
// Prefer creating small buffers <= 32 KB as committed, because drivers pack them better,
// while placed buffers require 64 KB alignment.
if(resourceDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER &&
resourceDesc.Width <= D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT / 2 &&
strategy != ALLOCATION_FLAG_STRATEGY_MIN_TIME && // Creating as committed would be slower.
!useTightAlignment &&
m_PreferSmallBuffersCommitted)
{
return true;
@@ -7526,7 +7686,7 @@ HRESULT AllocatorPimpl::AllocateHeap(
template<typename D3D12_RESOURCE_DESC_T>
HRESULT AllocatorPimpl::CalcAllocationParams(const ALLOCATION_DESC& allocDesc, UINT64 allocSize,
const D3D12_RESOURCE_DESC_T* resDesc,
const D3D12_RESOURCE_DESC_T* resDesc, bool useTightAlignment,
BlockVector*& outBlockVector, CommittedAllocationParameters& outCommittedAllocationParams, bool& outPreferCommitted)
{
outBlockVector = NULL;
@@ -7599,7 +7759,7 @@ HRESULT AllocatorPimpl::CalcAllocationParams(const ALLOCATION_DESC& allocDesc, U
{
if (resDesc->SampleDesc.Count > 1 && msaaAlwaysCommitted)
outBlockVector = NULL;
if (!outPreferCommitted && PrefersCommittedAllocation(*resDesc, allocDesc.Flags & ALLOCATION_FLAG_STRATEGY_MASK))
if (!outPreferCommitted && PrefersCommittedAllocation(*resDesc, allocDesc.Flags & ALLOCATION_FLAG_STRATEGY_MASK, useTightAlignment))
outPreferCommitted = true;
}
@@ -7810,12 +7970,13 @@ template<typename D3D12_RESOURCE_DESC_T>
HRESULT AllocatorPimpl::GetResourceAllocationInfo(
D3D12_RESOURCE_DESC_T& inOutResourceDesc,
UINT32 NumCastableFormats, const DXGI_FORMAT* pCastableFormats,
D3D12_RESOURCE_ALLOCATION_INFO& outAllocInfo) const
D3D12_RESOURCE_ALLOCATION_INFO& outAllocInfo,
bool useTightAlignment) const
{
#ifdef __ID3D12Device1_INTERFACE_DEFINED__
#if D3D12MA_TIGHT_ALIGNMENT_SUPPORTED
if (IsTightAlignmentEnabled())
if (useTightAlignment)
{
// Don't allow USE_TIGHT_ALIGNMENT together with ALLOW_CROSS_ADAPTER as there is a D3D Debug Layer error:
// D3D12 ERROR: ID3D12Device::GetResourceAllocationInfo: D3D12_RESOURCE_DESC::Flag D3D12_RESOURCE_FLAG_USE_TIGHT_ALIGNMENT will be ignored since D3D12_RESOURCE_FLAG_ALLOW_CROSS_ADAPTER is set. [ STATE_CREATION ERROR #599: CREATERESOURCE_INVALIDMISCFLAGS]
@@ -7839,7 +8000,7 @@ HRESULT AllocatorPimpl::GetResourceAllocationInfo(
*/
if (inOutResourceDesc.Alignment == 0 &&
inOutResourceDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER &&
!IsTightAlignmentEnabled())
!useTightAlignment)
{
outAllocInfo = {
AlignUp<UINT64>(inOutResourceDesc.Width, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT), // SizeInBytes
@@ -7852,16 +8013,33 @@ HRESULT AllocatorPimpl::GetResourceAllocationInfo(
HRESULT hr = S_OK;
#if D3D12MA_USE_SMALL_RESOURCE_PLACEMENT_ALIGNMENT
if (inOutResourceDesc.Alignment == 0 &&
(inOutResourceDesc.Flags & D3D12_RESOURCE_FLAG_USE_TIGHT_ALIGNMENT_COPY) == 0 &&
(inOutResourceDesc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE1D ||
inOutResourceDesc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D ||
inOutResourceDesc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D) &&
(inOutResourceDesc.Flags & (D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL)) == 0
bool trySmallAlignment = false;
if (inOutResourceDesc.Alignment == 0
&& (inOutResourceDesc.Flags & D3D12_RESOURCE_FLAG_USE_TIGHT_ALIGNMENT_COPY) == 0
&& inOutResourceDesc.Layout == D3D12_TEXTURE_LAYOUT_UNKNOWN
&& ResourceDimensionIsTexture(inOutResourceDesc.Dimension))
{
// MSAA texture.
if(inOutResourceDesc.SampleDesc.Count > 1)
{
trySmallAlignment = IsMsaa64KBAlignedTextureSupported()
&& ResourceFlagsContainRenderTargetOrDepthStencil(inOutResourceDesc.Flags)
&& inOutResourceDesc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D;
}
// Non-MSAA texture.
else
{
trySmallAlignment = !ResourceFlagsContainRenderTargetOrDepthStencil(inOutResourceDesc.Flags);
}
}
#if D3D12MA_USE_SMALL_RESOURCE_PLACEMENT_ALIGNMENT == 1
&& CanUseSmallAlignment(inOutResourceDesc)
if(trySmallAlignment)
trySmallAlignment = CanUseSmallAlignment(inOutResourceDesc);
#endif
)
if (trySmallAlignment)
{
/*
The algorithm here is based on Microsoft sample: "Small Resources Sample"
@@ -7886,6 +8064,20 @@ HRESULT AllocatorPimpl::GetResourceAllocationInfo(
inOutResourceDesc, NumCastableFormats, pCastableFormats, outAllocInfo);
}
bool AllocatorPimpl::IsTightAlignmentEnabled(const ALLOCATION_DESC& allocDesc) const
{
if (!IsTightAlignmentEnabled())
return false;
if (allocDesc.CustomPool != NULL &&
(allocDesc.CustomPool->m_Pimpl->GetDesc().Flags & POOL_FLAG_DONT_USE_TIGHT_ALIGNMENT) != 0)
{
return false;
}
return true;
}
bool AllocatorPimpl::NewAllocationWithinBudget(D3D12_HEAP_TYPE heapType, UINT64 size)
{
Budget budget = {};
+3
View File
@@ -22,6 +22,9 @@ find_package(Freetype 2.10 REQUIRED) # 2.10 is the first with COLRv0 support, wh
find_package(plutovg 1.1.0 REQUIRED)
find_package(plutosvg 0.0.7 REQUIRED)
find_package(ryml REQUIRED)
if (WIN32)
find_package(DirectX-Headers 1.618.1 REQUIRED)
endif()
if(USE_VULKAN)
find_package(Shaderc REQUIRED)
-2
View File
@@ -30,8 +30,6 @@ namespace x86Emitter
extern void EmitRex(SIMDInstructionInfo info, const xRegisterBase& reg1, const xRegisterBase& reg2);
extern void EmitRex(SIMDInstructionInfo info, const xRegisterBase& reg1, const xIndirectVoid& sib);
extern void _xMovRtoR(const xRegisterInt& to, const xRegisterInt& from);
template <typename T>
inline void xWrite(T val)
{
+1 -7
View File
@@ -22,7 +22,7 @@
namespace x86Emitter
{
void _xMovRtoR(const xRegisterInt& to, const xRegisterInt& from)
void xImpl_Mov::operator()(const xRegisterInt& to, const xRegisterInt& from) const
{
pxAssert(to.GetOperandSize() == from.GetOperandSize());
@@ -32,12 +32,6 @@ namespace x86Emitter
xOpWrite(from.GetPrefix16(), from.Is8BitOp() ? 0x88 : 0x89, from, to);
}
void xImpl_Mov::operator()(const xRegisterInt& to, const xRegisterInt& from) const
{
// FIXME WTF?
_xMovRtoR(to, from);
}
void xImpl_Mov::operator()(const xIndirectVoid& dest, const xRegisterInt& from) const
{
// mov eax has a special from when writing directly to a DISP32 address
+6 -6
View File
@@ -1045,7 +1045,7 @@ const xRegister32
}
else if (displacement_size == 0)
{
_xMovRtoR(to, src.Index.MatchSizeTo(to));
xMOV(to, src.Index.MatchSizeTo(to));
return;
}
else if (!preserve_flags)
@@ -1053,7 +1053,7 @@ const xRegister32
// encode as MOV and ADD combo. Make sure to use the immediate on the
// ADD since it can encode as an 8-bit sign-extended value.
_xMovRtoR(to, src.Index.MatchSizeTo(to));
xMOV(to, src.Index.MatchSizeTo(to));
xADD(to, src.Displacement);
return;
}
@@ -1071,7 +1071,7 @@ const xRegister32
// (this does not apply to older model P4s with the broken barrel shifter,
// but we currently aren't optimizing for that target anyway).
_xMovRtoR(to, src.Index);
xMOV(to, src.Index);
xSHL(to, src.Scale);
return;
}
@@ -1085,14 +1085,14 @@ const xRegister32
if (src.Index == rsp)
{
// ESP is not encodable as an index (ix86 ignores it), thus:
_xMovRtoR(to, src.Base.MatchSizeTo(to)); // will do the trick!
xMOV(to, src.Base.MatchSizeTo(to)); // will do the trick!
if (src.Displacement)
xADD(to, src.Displacement);
return;
}
else if (src.Displacement == 0)
{
_xMovRtoR(to, src.Base.MatchSizeTo(to));
xMOV(to, src.Base.MatchSizeTo(to));
xADD(to, src.Index.MatchSizeTo(to));
return;
}
@@ -1102,7 +1102,7 @@ const xRegister32
// special case handling of ESP as Index, which is replaceable with
// a single MOV even when preserve_flags is set! :D
_xMovRtoR(to, src.Base.MatchSizeTo(to));
xMOV(to, src.Base.MatchSizeTo(to));
return;
}
}
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<DepsRootDir Condition="'$(Platform)'=='x64'">$(SolutionDir)deps\</DepsRootDir>
<DepsRootDir Condition="'$(Platform)'=='ARM64'">$(SolutionDir)deps-arm64\</DepsRootDir>
<DepsBinDir>$(DepsRootDir)bin\</DepsBinDir>
<DepsLibDir>$(DepsRootDir)lib\</DepsLibDir>
<DepsIncludeDir>$(DepsRootDir)include\</DepsIncludeDir>
</PropertyGroup>
</Project>
+3 -5
View File
@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets">
<Import Project="DepsDir.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros">
<SSEString />
<SSEString Condition="$(Configuration.Contains(AVX2))">-avx2</SSEString>
@@ -15,11 +18,6 @@
<PropertyGroup>
<OutDir>$(SolutionDir)build\lib-$(PlatformName)-$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)build\obj-$(ProjectName)-$(PlatformName)-$(Configuration)\</IntDir>
<DepsRootDir Condition="'$(Platform)'=='x64'">$(SolutionDir)deps\</DepsRootDir>
<DepsRootDir Condition="'$(Platform)'=='ARM64'">$(SolutionDir)deps-arm64\</DepsRootDir>
<DepsBinDir>$(DepsRootDir)bin\</DepsBinDir>
<DepsLibDir>$(DepsRootDir)lib\</DepsLibDir>
<DepsIncludeDir>$(DepsRootDir)include\</DepsIncludeDir>
</PropertyGroup>
<PropertyGroup>
<LinkIncremental>false</LinkIncremental>
+301 -60
View File
@@ -36,6 +36,9 @@
#include "pcsx2/ImGui/FullscreenUI.h"
#include "pcsx2/MTGS.h"
#include "pcsx2/PerformanceMetrics.h"
#include "pcsx2/VMManager.h"
#include "pcsx2/ImGui/ImGuiOverlays.h"
#include "pcsx2/SPU2/spu2.h"
#include "pcsx2/Recording/InputRecording.h"
#include "pcsx2/Recording/InputRecordingControls.h"
#include "pcsx2/SaveState.h"
@@ -54,8 +57,11 @@
#include <QtWidgets/QInputDialog>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QProgressBar>
#include <QtWidgets/QSlider>
#include <QtWidgets/QStyle>
#include <QtWidgets/QStyleFactory>
#include <QtWidgets/QWidgetAction>
#include <QtWidgets/QHBoxLayout>
#ifdef _WIN32
#include "common/RedtapeWindows.h"
@@ -147,6 +153,14 @@ void MainWindow::initialize()
switchToGameListView();
updateWindowTitle();
updateGameDependentActions();
updateEmulationActions(false, s_vm_valid, false);
updateStatusBarWidgetVisibility();
if (s_vm_paused)
{
m_ui.actionPause->setChecked(true);
m_ui.actionToolbarPause->setChecked(true);
}
#ifdef _WIN32
registerForDeviceNotifications();
@@ -185,6 +199,7 @@ void MainWindow::setupAdditionalUi()
{
makeIconsMasks(menuBar());
updateAdvancedSettingsVisibility();
setupStatusBarWidgets();
const bool toolbar_visible = Host::GetBaseBoolSettingValue("UI", "ShowToolbar", false);
m_ui.actionViewToolbar->setChecked(toolbar_visible);
@@ -195,58 +210,6 @@ void MainWindow::setupAdditionalUi()
m_ui.toolBar->setMovable(!toolbars_locked);
m_ui.toolBar->setContextMenuPolicy(Qt::PreventContextMenu);
const bool status_bar_visible = Host::GetBaseBoolSettingValue("UI", "ShowStatusBar", true);
m_ui.actionViewStatusBar->setChecked(status_bar_visible);
m_ui.statusBar->setVisible(status_bar_visible);
const bool show_game_grid = Host::GetBaseBoolSettingValue("UI", "GameListGridView", false);
updateGameGridActions(show_game_grid);
m_game_list_widget = new GameListWidget(getContentParent());
m_game_list_widget->initialize();
m_ui.actionGridViewShowTitles->setChecked(m_game_list_widget->getShowGridCoverTitles());
m_ui.mainContainer->addWidget(m_game_list_widget);
m_status_progress_widget = new QProgressBar(m_ui.statusBar);
m_status_progress_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
m_status_progress_widget->setFixedSize(140, 16);
m_status_progress_widget->setMinimum(0);
m_status_progress_widget->setMaximum(100);
m_status_progress_widget->hide();
m_status_verbose_widget = new QLabel(m_ui.statusBar);
m_status_verbose_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
m_status_verbose_widget->setFixedHeight(16);
m_status_verbose_widget->hide();
m_status_renderer_widget = new QLabel(m_ui.statusBar);
m_status_renderer_widget->setFixedHeight(16);
m_status_renderer_widget->setFixedSize(65, 16);
m_status_renderer_widget->hide();
m_status_resolution_widget = new QLabel(m_ui.statusBar);
m_status_resolution_widget->setFixedHeight(16);
m_status_resolution_widget->setFixedSize(75, 16);
m_status_resolution_widget->hide();
m_status_fps_widget = new QLabel(m_ui.statusBar);
m_status_fps_widget->setFixedHeight(16);
m_status_fps_widget->setMinimumWidth(60);
m_status_fps_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
m_status_fps_widget->hide();
m_status_vps_widget = new QLabel(m_ui.statusBar);
m_status_vps_widget->setFixedHeight(16);
m_status_vps_widget->setMinimumWidth(60);
m_status_vps_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
m_status_vps_widget->hide();
m_status_speed_widget = new QLabel(m_ui.statusBar);
m_status_speed_widget->setFixedHeight(16);
m_status_speed_widget->setMinimumWidth(130);
m_status_speed_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
m_status_speed_widget->hide();
m_settings_toolbar_menu = new QMenu(m_ui.toolBar);
m_settings_toolbar_menu->addAction(m_ui.actionSettings);
m_settings_toolbar_menu->addAction(m_ui.actionViewGameProperties);
@@ -257,6 +220,14 @@ void MainWindow::setupAdditionalUi()
connect(action, &QAction::triggered, [scale]() { g_emu_thread->requestDisplaySize(static_cast<float>(scale)); });
}
const bool show_game_grid = Host::GetBaseBoolSettingValue("UI", "GameListGridView", false);
updateGameGridActions(show_game_grid);
m_game_list_widget = new GameListWidget(getContentParent());
m_game_list_widget->initialize();
m_ui.actionGridViewShowTitles->setChecked(m_game_list_widget->getShowGridCoverTitles());
m_ui.mainContainer->addWidget(m_game_list_widget);
updateEmulationActions(false, false, false);
updateDisplayRelatedActions(false, false, false);
@@ -292,6 +263,216 @@ void MainWindow::setupAdditionalUi()
#endif
}
void MainWindow::setupStatusBarWidgets()
{
const bool status_bar_visible = Host::GetBaseBoolSettingValue("UI", "ShowStatusBar", true);
m_ui.actionViewStatusBar->setChecked(status_bar_visible);
m_ui.statusBar->setVisible(status_bar_visible);
m_status_progress_widget = new QProgressBar(m_ui.statusBar);
m_status_progress_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
m_status_progress_widget->setFixedSize(140, 16);
m_status_progress_widget->setMinimum(0);
m_status_progress_widget->setMaximum(100);
m_status_progress_widget->hide();
m_status_verbose_widget = new QLabel(m_ui.statusBar);
m_status_verbose_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
m_status_verbose_widget->setContentsMargins(10, 0, 10, 0);
m_status_verbose_widget->setFixedHeight(20);
m_status_verbose_widget->setMinimumWidth(20);
m_status_verbose_widget->hide();
m_status_renderer_widget = new QLabel(m_ui.statusBar);
m_status_renderer_widget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
m_status_renderer_widget->setContentsMargins(10, 0, 10, 0);
m_status_renderer_widget->setAlignment(Qt::AlignCenter);
m_status_renderer_widget->setFixedHeight(20);
m_status_renderer_widget->hide();
m_status_resolution_widget = new QLabel(m_ui.statusBar);
m_status_resolution_widget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
m_status_resolution_widget->setContentsMargins(10, 0, 10, 0);
m_status_resolution_widget->setAlignment(Qt::AlignCenter);
m_status_resolution_widget->setFixedHeight(20);
m_status_resolution_widget->hide();
m_status_volume_widget = new QToolButton(m_ui.statusBar);
m_status_volume_widget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
m_status_volume_widget->setContentsMargins(10, 0, 10, 0);
m_status_volume_widget->setAutoRaise(true);
m_status_volume_widget->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
m_status_volume_widget->setIcon(QIcon::fromTheme(QStringLiteral("volume-up-line")));
m_status_volume_widget->setPopupMode(QToolButton::InstantPopup);
m_status_volume_widget->setFixedHeight(20);
m_status_volume_menu = new QMenu(m_status_volume_widget);
m_status_volume_per_game_action = new QAction(tr("Adjust Per-Game"), m_status_volume_menu);
m_status_volume_per_game_action->setCheckable(true);
connect(m_status_volume_menu, &QMenu::aboutToShow, this, [this]() {
auto lock = Host::GetSettingsLock();
SettingsInterface* game_layer = Host::Internal::GetGameSettingsLayer();
m_status_volume_per_game_action->setEnabled(game_layer != nullptr && QtHost::IsVMValid());
m_status_volume_per_game_action->setChecked(game_layer &&
(game_layer->ContainsValue("SPU2/Output", "StandardVolume") || game_layer->ContainsValue("SPU2/Output", "OutputMuted")));
});
connect(m_status_volume_per_game_action, &QAction::triggered, this, [this](bool checked) {
m_status_volume_slider_applied = true;
applyStatusBarVolumeChanges(std::nullopt, false, checked);
});
m_status_volume_toggle_mute_action = new QAction(tr("Toggle Mute"), m_status_volume_menu);
m_status_volume_toggle_mute_action->setIcon(QIcon::fromTheme(QStringLiteral("volume-mute-line")));
connect(m_status_volume_toggle_mute_action, &QAction::triggered, this, [this]() {
m_status_volume_slider_applied = true;
applyStatusBarVolumeChanges(std::nullopt, true, std::nullopt);
});
m_status_volume_menu->addAction(m_status_volume_per_game_action);
m_status_volume_menu->addAction(m_status_volume_toggle_mute_action);
m_status_volume_menu->addSeparator();
m_status_volume_slider = new QSlider(Qt::Horizontal, m_status_volume_menu);
m_status_volume_slider->setRange(0, 100);
m_status_volume_slider->setFixedWidth(120);
m_status_volume_slider->setValue(Host::GetIntSettingValue("SPU2/Output", "StandardVolume", 100));
connect(m_status_volume_slider, &QSlider::valueChanged, this, [this](int value) {
Host::RunOnCPUThread([value]() {
if (!VMManager::HasValidVM())
return;
EmuConfig.SPU2.StandardVolume = static_cast<u32>(value);
SPU2::SetOutputVolume(static_cast<u32>(value));
});
if (m_status_volume_slider->isSliderDown())
setStatusVolumeText(m_status_volume_muted ? tr("Volume: Muted") : tr("Volume: %1%").arg(value), value, m_status_volume_muted);
});
connect(m_status_volume_slider, &QSlider::sliderReleased, this, [this]() {
m_status_volume_slider_applied = true;
applyStatusBarVolumeChanges(m_status_volume_slider->value(), false);
});
connect(m_status_volume_menu, &QMenu::aboutToHide, this, [this]() {
if (!m_status_volume_slider_applied)
applyStatusBarVolumeChanges(m_status_volume_slider->value(), false);
m_status_volume_slider_applied = false;
});
QWidget* container = new QWidget(m_status_volume_menu);
QHBoxLayout* layout = new QHBoxLayout(container);
layout->setContentsMargins(8, 4, 8, 4);
layout->addWidget(m_status_volume_slider);
QWidgetAction* slider_action = new QWidgetAction(m_status_volume_menu);
slider_action->setDefaultWidget(container);
m_status_volume_menu->addAction(slider_action);
m_status_volume_widget->setMenu(m_status_volume_menu);
m_status_volume_widget->hide();
m_status_speed_widget = new QToolButton(m_ui.statusBar);
m_status_speed_widget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
m_status_speed_widget->setContentsMargins(10, 0, 10, 0);
m_status_speed_widget->setAutoRaise(true);
m_status_speed_widget->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
m_status_speed_widget->setPopupMode(QToolButton::InstantPopup);
m_status_speed_widget->setFixedHeight(20);
m_status_speed_widget->hide();
m_status_speed_menu = new QMenu(m_status_speed_widget);
m_status_speed_menu->addAction(tr("Unlimited"), []() { Host::RunOnCPUThread([]() { VMManager::SetLimiterMode(LimiterModeType::Unlimited); }); });
m_status_speed_menu->addAction(tr("Turbo"), []() { Host::RunOnCPUThread([]() { VMManager::SetLimiterMode(LimiterModeType::Turbo); }); });
m_status_speed_menu->addAction(tr("Slow-Motion"), []() { Host::RunOnCPUThread([]() { VMManager::SetLimiterMode(LimiterModeType::Slomo); }); });
m_status_speed_menu->addAction(tr("Normal"), []() { Host::RunOnCPUThread([]() { VMManager::SetLimiterMode(LimiterModeType::Nominal); }); });
m_status_speed_widget->setMenu(m_status_speed_menu);
m_status_speed_widget->hide();
m_status_gpu_widget = new QLabel(m_ui.statusBar);
m_status_gpu_widget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
m_status_gpu_widget->setContentsMargins(10, 0, 10, 0);
m_status_gpu_widget->setAlignment(Qt::AlignCenter);
m_status_gpu_widget->setFixedHeight(20);
m_status_gpu_widget->hide();
m_status_fps_widget = new QLabel(m_ui.statusBar);
m_status_fps_widget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
m_status_fps_widget->setContentsMargins(10, 0, 10, 0);
m_status_fps_widget->setAlignment(Qt::AlignCenter);
m_status_fps_widget->setFixedHeight(20);
m_status_fps_widget->hide();
m_status_vps_widget = new QLabel(m_ui.statusBar);
m_status_vps_widget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
m_status_vps_widget->setContentsMargins(10, 0, 10, 0);
m_status_vps_widget->setAlignment(Qt::AlignCenter);
m_status_vps_widget->setFixedHeight(20);
m_status_vps_widget->hide();
}
void MainWindow::applyStatusBarVolumeChanges(std::optional<int> volume, bool toggle_mute, std::optional<bool> override_per_game)
{
const bool use_per_game = override_per_game.value_or(m_status_volume_per_game_action->isChecked());
Host::RunOnCPUThread([this, volume, toggle_mute, use_per_game]() {
int target_vol = 100;
bool target_mute = false;
{
auto lock = Host::GetSettingsLock();
SettingsInterface* per_game_settings = Host::Internal::GetGameSettingsLayer();
SettingsInterface* global_settings = Host::Internal::GetBaseSettingsLayer();
SettingsInterface* active_setting = (use_per_game && per_game_settings) ? per_game_settings : global_settings;
if (active_setting)
{
if (!active_setting->GetIntValue("SPU2/Output", "StandardVolume", &target_vol) && global_settings)
global_settings->GetIntValue("SPU2/Output", "StandardVolume", &target_vol);
if (!active_setting->GetBoolValue("SPU2/Output", "OutputMuted", &target_mute) && global_settings)
global_settings->GetBoolValue("SPU2/Output", "OutputMuted", &target_mute);
}
target_vol = volume.value_or(target_vol);
if (toggle_mute)
target_mute = !(VMManager::HasValidVM() ? SPU2::IsOutputMuted() : target_mute);
if (active_setting)
{
active_setting->SetIntValue("SPU2/Output", "StandardVolume", target_vol);
active_setting->SetBoolValue("SPU2/Output", "OutputMuted", target_mute);
}
if (per_game_settings && active_setting == per_game_settings)
QtHost::SaveGameSettings(per_game_settings, true);
else
{
if (per_game_settings)
{
const bool had_per_game_volume =
per_game_settings->ContainsValue("SPU2/Output", "StandardVolume") ||
per_game_settings->ContainsValue("SPU2/Output", "OutputMuted");
per_game_settings->DeleteValue("SPU2/Output", "StandardVolume");
per_game_settings->DeleteValue("SPU2/Output", "OutputMuted");
if (had_per_game_volume)
QtHost::SaveGameSettings(per_game_settings, true);
}
Host::CommitBaseSettingChanges();
}
}
if (VMManager::HasValidVM())
{
EmuConfig.SPU2.StandardVolume = static_cast<u32>(target_vol);
EmuConfig.SPU2.OutputMuted = target_mute;
SPU2::SetOutputMuted(target_mute);
SPU2::SetOutputVolume(static_cast<u32>(target_vol));
}
QTimer::singleShot(0, this, [this, target_vol, target_mute]() {
setStatusVolumeText(target_mute ? tr("Volume: Muted") : tr("Volume: %1%").arg(target_vol), target_vol, target_mute);
});
});
}
void MainWindow::connectSignals()
{
connect(m_ui.actionStartFile, &QAction::triggered, this, &MainWindow::onStartFileActionTriggered);
@@ -496,12 +677,11 @@ void MainWindow::createRendererSwitchMenu()
QString::fromUtf8(Pcsx2Config::GSOptions::GetRendererName(renderer)), switch_renderer_group);
action->setCheckable(true);
action->setChecked(current_renderer == renderer);
connect(action,
&QAction::triggered, [renderer] {
Host::SetBaseIntSettingValue("EmuCore/GS", "Renderer", static_cast<int>(renderer));
Host::CommitBaseSettingChanges();
g_emu_thread->applySettings();
});
connect(action, &QAction::triggered, [renderer] {
Host::SetBaseIntSettingValue("EmuCore/GS", "Renderer", static_cast<int>(renderer));
Host::CommitBaseSettingChanges();
g_emu_thread->applySettings();
});
}
m_ui.menuDebugSwitchRenderer->addActions(switch_renderer_group->actions());
@@ -548,6 +728,9 @@ void MainWindow::recreate()
g_main_window->updateEmulationActions(false, s_vm_valid, false);
g_main_window->onFullscreenUIStateChange(g_emu_thread->isRunningFullscreenUI());
}
if (s_vm_valid)
g_emu_thread->updatePerformanceMetrics(true);
}
void MainWindow::recreateSettings()
@@ -1019,13 +1202,15 @@ void MainWindow::updateDisplayRelatedActions(bool has_surface, bool render_to_ma
void MainWindow::updateStatusBarWidgetVisibility()
{
auto Update = [this](QWidget* widget, bool visible, int stretch) {
if (widget->isVisible() == visible)
return;
if (widget->isVisible())
{
m_ui.statusBar->removeWidget(widget);
widget->hide();
}
if (visible)
else
{
m_ui.statusBar->addPermanentWidget(widget, stretch);
widget->show();
@@ -1035,9 +1220,63 @@ void MainWindow::updateStatusBarWidgetVisibility()
Update(m_status_verbose_widget, s_vm_valid, 1);
Update(m_status_renderer_widget, s_vm_valid, 0);
Update(m_status_resolution_widget, s_vm_valid, 0);
Update(m_status_gpu_widget, s_vm_valid, 0);
Update(m_status_fps_widget, s_vm_valid, 0);
Update(m_status_vps_widget, s_vm_valid, 0);
Update(m_status_speed_widget, s_vm_valid, 0);
Update(m_status_volume_widget, s_vm_valid, 0);
}
void MainWindow::setStatusVerboseText(const QString& text)
{
m_status_verbose_widget->setText(text);
}
void MainWindow::setStatusRendererText(const QString& text)
{
m_status_renderer_widget->setText(text);
}
void MainWindow::setStatusResolutionText(const QString& text)
{
m_status_resolution_widget->setText(text);
}
void MainWindow::setStatusVolumeText(const QString& text, int volume, bool muted)
{
m_status_volume_widget->setText(text);
if (muted != m_status_volume_muted)
{
m_status_volume_muted = muted;
m_status_volume_widget->setIcon(QIcon::fromTheme(muted ? QStringLiteral("volume-mute-line") : QStringLiteral("volume-up-line")));
m_status_volume_toggle_mute_action->setIcon(QIcon::fromTheme(muted ? QStringLiteral("volume-up-line") : QStringLiteral("volume-mute-line")));
}
if (!muted && !m_status_volume_slider->isSliderDown())
{
QSignalBlocker blocker(m_status_volume_slider);
m_status_volume_slider->setValue(volume);
}
}
void MainWindow::setStatusGPUText(const QString& text)
{
m_status_gpu_widget->setText(text);
}
void MainWindow::setStatusFPSText(const QString& text)
{
m_status_fps_widget->setText(text);
}
void MainWindow::setStatusVPSText(const QString& text)
{
m_status_vps_widget->setText(text);
}
void MainWindow::setStatusSpeedText(const QString& text)
{
m_status_speed_widget->setText(text);
}
void MainWindow::updateWindowTitle()
@@ -2218,6 +2457,8 @@ void MainWindow::onVMStopped()
m_last_fps_status = empty_string;
m_status_renderer_widget->setText(empty_string);
m_status_resolution_widget->setText(empty_string);
m_status_volume_widget->setText(empty_string);
m_status_gpu_widget->setText(empty_string);
m_status_fps_widget->setText(empty_string);
m_status_vps_widget->setText(empty_string);
m_status_speed_widget->setText(empty_string);
+26 -2
View File
@@ -8,6 +8,8 @@
#include <QtWidgets/QLabel>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMenu>
#include <QtWidgets/QSlider>
#include <QtWidgets/QToolButton>
#include <functional>
#include <optional>
@@ -96,11 +98,13 @@ public:
/// Accessors for the status bar widgets, updated by the emulation thread.
__fi QLabel* getStatusVerboseWidget() const { return m_status_verbose_widget; }
__fi QToolButton* getStatusSpeedWidget() const { return m_status_speed_widget; }
__fi QToolButton* getStatusVolumeWidget() const { return m_status_volume_widget; }
__fi QLabel* getStatusRendererWidget() const { return m_status_renderer_widget; }
__fi QLabel* getStatusResolutionWidget() const { return m_status_resolution_widget; }
__fi QLabel* getStatusGPUWidget() const { return m_status_gpu_widget; }
__fi QLabel* getStatusFPSWidget() const { return m_status_fps_widget; }
__fi QLabel* getStatusVPSWidget() const { return m_status_vps_widget; }
__fi QLabel* getStatusSpeedWidget() const { return m_status_speed_widget; }
/// Rescans a single file. NOTE: Happens on UI thread.
void rescanFile(const std::string& path);
@@ -125,6 +129,15 @@ public Q_SLOTS:
void reportStateLoadError(const QString& message, std::optional<s32> slot, bool backup);
void reportStateSaveError(const QString& message, std::optional<s32> slot);
void setStatusVerboseText(const QString& text);
void setStatusRendererText(const QString& text);
void setStatusResolutionText(const QString& text);
void setStatusVolumeText(const QString& text, int volume, bool muted);
void setStatusGPUText(const QString& text);
void setStatusFPSText(const QString& text);
void setStatusVPSText(const QString& text);
void setStatusSpeedText(const QString& text);
void runOnUIThread(const std::function<void()>& func);
void requestReset();
bool requestShutdown(bool allow_confirm = true, bool allow_save_to_state = true, bool default_save_to_state = true);
@@ -227,6 +240,8 @@ protected:
private:
void setupAdditionalUi();
void setupStatusBarWidgets();
void applyStatusBarVolumeChanges(std::optional<int> volume, bool toggle_mute, std::optional<bool> override_per_game = std::nullopt);
void connectSignals();
void createRendererSwitchMenu();
void recreate();
@@ -309,14 +324,23 @@ private:
QProgressBar* m_status_progress_widget = nullptr;
QLabel* m_status_verbose_widget = nullptr;
QLabel* m_status_renderer_widget = nullptr;
QToolButton* m_status_volume_widget = nullptr;
QMenu* m_status_volume_menu = nullptr;
QAction* m_status_volume_per_game_action = nullptr;
QAction* m_status_volume_toggle_mute_action = nullptr;
QSlider* m_status_volume_slider = nullptr;
QLabel* m_status_gpu_widget = nullptr;
QLabel* m_status_fps_widget = nullptr;
QLabel* m_status_vps_widget = nullptr;
QLabel* m_status_speed_widget = nullptr;
QToolButton* m_status_speed_widget = nullptr;
QMenu* m_status_speed_menu = nullptr;
QLabel* m_status_resolution_widget = nullptr;
QMenu* m_settings_toolbar_menu = nullptr;
bool m_display_created = false;
bool m_status_volume_muted = false;
bool m_status_volume_slider_applied = false;
bool m_display_is_exclusive_fullscreen = false;
bool m_relative_mouse_mode = false;
bool m_hide_mouse_cursor = false;
+78 -23
View File
@@ -433,10 +433,16 @@ void EmuThread::run()
void EmuThread::destroyVM()
{
m_last_speed = 0.0f;
m_last_gpu_usage = 0.0f;
m_last_game_fps = 0.0f;
m_last_video_fps = 0.0f;
m_last_internal_width = 0;
m_last_internal_height = 0;
m_last_upscale = 0.0f;
m_last_volume = 0;
m_last_muted = false;
m_last_renderer = GSRendererType::Auto;
m_last_limiter_mode = LimiterModeType::Nominal;
m_was_paused_by_focus_loss = false;
VMManager::Shutdown(m_save_state_on_shutdown);
m_save_state_on_shutdown = false;
@@ -970,8 +976,15 @@ void EmuThread::updatePerformanceMetrics(bool force)
if (!g_main_window)
return;
const s32 slot = SaveStateSelectorUI::GetCurrentSlot();
u32 volume = 0;
bool muted = false;
if (VMManager::HasValidVM())
{
volume = SPU2::GetOutputVolume();
muted = SPU2::IsOutputMuted();
QString gs_stat;
if (m_verbose_status)
{
@@ -980,9 +993,8 @@ void EmuThread::updatePerformanceMetrics(bool force)
if (THREAD_VU1)
{
gs_stat = tr("Slot: %1 | Volume: %2% | %3 | EE: %4% | VU: %5% | GS: %6%")
.arg(SaveStateSelectorUI::GetCurrentSlot())
.arg(SPU2::GetOutputVolume())
gs_stat = tr("Slot: %1 | %2 | EE: %3% | VU: %4% | GS: %5%")
.arg(slot)
.arg(gs_stat_str.c_str())
.arg(PerformanceMetrics::GetCPUThreadUsage(), 0, 'f', 0)
.arg(PerformanceMetrics::GetVUThreadUsage(), 0, 'f', 0)
@@ -990,48 +1002,81 @@ void EmuThread::updatePerformanceMetrics(bool force)
}
else
{
gs_stat = tr("Slot: %1 | Volume: %2% | %3 | EE: %4% | GS: %5%")
.arg(SaveStateSelectorUI::GetCurrentSlot())
.arg(SPU2::GetOutputVolume())
gs_stat = tr("Slot: %1 | %2 | EE: %3% | GS: %4%")
.arg(slot)
.arg(gs_stat_str.c_str())
.arg(PerformanceMetrics::GetCPUThreadUsage(), 0, 'f', 0)
.arg(PerformanceMetrics::GetGSThreadUsage(), 0, 'f', 0);
}
}
QMetaObject::invokeMethod(g_main_window->getStatusVerboseWidget(), "setText", Qt::QueuedConnection, Q_ARG(const QString&, gs_stat));
QMetaObject::invokeMethod(g_main_window, "setStatusVerboseText", Qt::QueuedConnection, Q_ARG(const QString&, gs_stat));
}
const GSRendererType renderer = GSGetCurrentRenderer(); // Reading from GS thread, therefore racey, but it's just visual.
const float upscale = EmuConfig.GS.UpscaleMultiplier;
const float speed = std::round(PerformanceMetrics::GetSpeed());
const LimiterModeType limiter_mode = VMManager::GetLimiterMode();
const float gpu_usage = std::round(PerformanceMetrics::GetGPUUsage());
const float gfps = std::round(PerformanceMetrics::GetInternalFPS());
const float vfps = std::round(PerformanceMetrics::GetFPS());
int iwidth, iheight;
GSgetInternalResolution(&iwidth, &iheight);
if (iwidth != m_last_internal_width || iheight != m_last_internal_height || speed != m_last_speed || gfps != m_last_game_fps ||
vfps != m_last_video_fps || renderer != m_last_renderer || force)
if (iwidth != m_last_internal_width || iheight != m_last_internal_height || upscale != m_last_upscale ||
speed != m_last_speed || gpu_usage != m_last_gpu_usage || gfps != m_last_game_fps || vfps != m_last_video_fps || renderer != m_last_renderer ||
volume != m_last_volume || muted != m_last_muted || force)
{
if (volume != m_last_volume || muted != m_last_muted || force)
{
QString vol_text = tr("Volume: %1%").arg(volume);
if (muted)
vol_text = tr("Volume: Muted");
QMetaObject::invokeMethod(g_main_window, "setStatusVolumeText", Qt::QueuedConnection,
Q_ARG(const QString&, vol_text), Q_ARG(int, static_cast<int>(volume)), Q_ARG(bool, muted));
m_last_volume = volume;
m_last_muted = muted;
}
if (renderer != m_last_renderer || force)
{
QMetaObject::invokeMethod(g_main_window->getStatusRendererWidget(), "setText", Qt::QueuedConnection,
Q_ARG(const QString&, QString::fromUtf8(Pcsx2Config::GSOptions::GetRendererName(renderer))));
QString renderer_name = QString::fromUtf8(Pcsx2Config::GSOptions::GetRendererName(renderer));
if (EmuConfig.GS.Renderer == GSRendererType::Auto && renderer != GSRendererType::Auto)
renderer_name = tr("Auto (%1)").arg(renderer_name);
QMetaObject::invokeMethod(g_main_window, "setStatusRendererText", Qt::QueuedConnection,
Q_ARG(const QString&, renderer_name));
m_last_renderer = renderer;
}
if (iwidth != m_last_internal_width || iheight != m_last_internal_height || force)
if (iwidth != m_last_internal_width || iheight != m_last_internal_height || upscale != m_last_upscale || force)
{
QString text;
if (iwidth == 0 || iheight == 0)
text = tr("No Image");
else
text = tr("%1x%2").arg(iwidth).arg(iheight);
text = tr("%1x%2 (%3x)").arg(iwidth).arg(iheight).arg(upscale, 0, 'g', 3);
QMetaObject::invokeMethod(
g_main_window->getStatusResolutionWidget(), "setText", Qt::QueuedConnection, Q_ARG(const QString&, text));
g_main_window, "setStatusResolutionText", Qt::QueuedConnection, Q_ARG(const QString&, text));
m_last_internal_width = iwidth;
m_last_internal_height = iheight;
m_last_upscale = upscale;
}
if (gpu_usage != m_last_gpu_usage || force)
{
QString text;
if (gpu_usage == 0)
text = tr("GPU: N/A");
else
text = tr("GPU: %1%").arg(gpu_usage, 0, 'f', 0);
QMetaObject::invokeMethod(g_main_window, "setStatusGPUText", Qt::QueuedConnection,
Q_ARG(const QString&, text));
m_last_gpu_usage = gpu_usage;
}
if (gfps != m_last_game_fps || force)
@@ -1042,23 +1087,33 @@ void EmuThread::updatePerformanceMetrics(bool force)
else
text = tr("FPS: %1").arg(gfps, 0, 'f', 0);
QMetaObject::invokeMethod(g_main_window->getStatusFPSWidget(), "setText", Qt::QueuedConnection,
QMetaObject::invokeMethod(g_main_window, "setStatusFPSText", Qt::QueuedConnection,
Q_ARG(const QString&, text));
m_last_game_fps = gfps;
}
if (vfps != m_last_video_fps || force)
{
QMetaObject::invokeMethod(g_main_window->getStatusVPSWidget(), "setText", Qt::QueuedConnection,
Q_ARG(const QString&, tr("VPS: %1 ").arg(vfps, 0, 'f', 0)));
QMetaObject::invokeMethod(g_main_window, "setStatusVPSText", Qt::QueuedConnection,
Q_ARG(const QString&, vfps == 0 ? tr("VPS: N/A") : tr("VPS: %1").arg(vfps, 0, 'f', 0)));
m_last_video_fps = vfps;
}
if (speed != m_last_speed || force)
{
QMetaObject::invokeMethod(g_main_window->getStatusSpeedWidget(), "setText", Qt::QueuedConnection,
Q_ARG(const QString&, tr("Speed: %1% ").arg(speed, 0, 'f', 0)));
m_last_speed = speed;
}
if (speed != m_last_speed || force)
{
QMetaObject::invokeMethod(g_main_window, "setStatusSpeedText", Qt::QueuedConnection,
Q_ARG(const QString&, tr("Speed: %1%").arg(speed, 0, 'f', 0)));
m_last_speed = speed;
}
if (limiter_mode != m_last_limiter_mode || force)
{
QMetaObject::invokeMethod(g_main_window, [limiter_mode]() { g_main_window->getStatusSpeedWidget()->setIcon(QIcon::fromTheme(
limiter_mode == LimiterModeType::Turbo ? QStringLiteral("fast-forward-line") :
limiter_mode == LimiterModeType::Slomo ? QStringLiteral("slow-mo") :
limiter_mode == LimiterModeType::Unlimited ? QStringLiteral("speed-line") :
QStringLiteral("dashboard-line"))); }, Qt::QueuedConnection);
m_last_limiter_mode = limiter_mode;
}
}
}
+5
View File
@@ -212,11 +212,16 @@ private:
bool m_was_paused_by_focus_loss = false;
float m_last_speed = 0.0f;
float m_last_gpu_usage = 0.0f;
float m_last_game_fps = 0.0f;
float m_last_video_fps = 0.0f;
int m_last_internal_width = 0;
int m_last_internal_height = 0;
float m_last_upscale = 0.0f;
u32 m_last_volume = 0;
bool m_last_muted = false;
GSRendererType m_last_renderer = GSRendererType::Auto;
LimiterModeType m_last_limiter_mode = LimiterModeType::Nominal;
};
extern EmuThread* g_emu_thread;
+1 -1
View File
@@ -89,7 +89,7 @@ ShortcutCreationDialog::ShortcutCreationDialog(QWidget* parent, const QString& t
}
m_ui.iconPath->setText(Path::ToNativePath(icon_file.toStdString()).c_str());
m_ui.iconPreview->setPixmap(pixmap.scaled(m_ui.iconPreview->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
m_ui.iconPreview->setPixmap(pixmap.scaled(m_ui.iconPreview->size(), Qt::KeepAspectRatio, Qt::FastTransformation));
m_ui.resetIconButton->setEnabled(true);
}
});
@@ -0,0 +1 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10-10-4.477-10-10 4.477-10 10-10m0 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16m3.833 3.337a.596.596 0 0 1 .763.067.59.59 0 0 1 .063.76q-3.27 4.569-3.598 4.897a1.5 1.5 0 0 1 -2.122-2.122q.56-.56 4.894-3.602m1.667 3.663a1 1 0 1 1 0 2 1 1 0 0 1 0-2m-11 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2m2.318-3.596a1 1 0 1 1 -1.414 1.414 1 1 0 0 1 1.414-1.414m3.182-1.904a1 1 0 1 1 0 2 1 1 0 0 1 0-2"/></svg>

After

Width:  |  Height:  |  Size: 507 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M22 4a1 1 0 0 0-1 1v5.666l-9.223-6.148a.5.5 0 0 0-.777.416v5.732L1.777 4.518A.5.5 0 0 0 1 4.934v14.132a.5.5 0 0 0 .777.416L11 13.333v5.733a.5.5 0 0 0 .777.416L21 13.333V19a1 1 0 1 0 2 0V5a1 1 0 0 0-1-1M3 7.737L9.394 12L3 16.263zm10 8.526V7.737L19.394 12z" />
</svg>

After

Width:  |  Height:  |  Size: 360 B

@@ -0,0 +1 @@
<svg height="512" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round"><path d="m158.61169 32.411431 327.37685 189.011119-327.37685 189.01111z" stroke-width="43.2072" transform="matrix(.75450544 0 0 .77725456 100.32662 82.344678)"/><path d="m106.51372 100v309.27423" stroke-width="76.6719"/></g></svg>

After

Width:  |  Height:  |  Size: 393 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="m12 13.333l-9.223 6.149A.5.5 0 0 1 2 19.066V4.934a.5.5 0 0 1 .777-.416L12 10.667V4.934a.5.5 0 0 1 .777-.416l10.599 7.066a.5.5 0 0 1 0 .832l-10.599 7.066a.5.5 0 0 1-.777-.416zM10.394 12L4 7.737v8.526zM14 7.737v8.526L20.394 12z" />
</svg>

After

Width:  |  Height:  |  Size: 331 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M10 7.22L6.603 10H3v4h3.603L10 16.78zM5.889 16H2a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1h3.889l5.294-4.332a.5.5 0 0 1 .817.387v15.89a.5.5 0 0 1-.817.387zm14.525-4l3.536 3.536l-1.415 1.414L19 13.414l-3.536 3.536l-1.414-1.414L17.586 12L14.05 8.465l1.414-1.415L19 10.586l3.535-3.536l1.415 1.415z"/></svg>

After

Width:  |  Height:  |  Size: 403 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="#fff" d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2m0 2a8 8 0 1 0 0 16a8 8 0 0 0 0-16m3.833 3.337a.596.596 0 0 1 .763.067a.59.59 0 0 1 .063.76q-3.27 4.569-3.598 4.897a1.5 1.5 0 0 1-2.122-2.122q.56-.56 4.894-3.602M17.5 11a1 1 0 1 1 0 2a1 1 0 0 1 0-2m-11 0a1 1 0 1 1 0 2a1 1 0 0 1 0-2m2.318-3.596a1 1 0 1 1-1.414 1.414a1 1 0 0 1 1.414-1.414M12 5.5a1 1 0 1 1 0 2a1 1 0 0 1 0-2" />
</svg>

After

Width:  |  Height:  |  Size: 510 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="#fff" d="M22 4a1 1 0 0 0-1 1v5.666l-9.223-6.148a.5.5 0 0 0-.777.416v5.732L1.777 4.518A.5.5 0 0 0 1 4.934v14.132a.5.5 0 0 0 .777.416L11 13.333v5.733a.5.5 0 0 0 .777.416L21 13.333V19a1 1 0 1 0 2 0V5a1 1 0 0 0-1-1M3 7.737L9.394 12L3 16.263zm10 8.526V7.737L19.394 12z" />
</svg>

After

Width:  |  Height:  |  Size: 372 B

@@ -0,0 +1 @@
<svg height="512" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round"><path d="m158.61169 32.411431 327.37685 189.011119-327.37685 189.01111z" stroke-width="43.2072" transform="matrix(.75450544 0 0 .77725456 100.32662 82.344678)"/><path d="m106.51372 100v309.27423" stroke-width="76.6719"/></g></svg>

After

Width:  |  Height:  |  Size: 393 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="#fff" d="m12 13.333l-9.223 6.149A.5.5 0 0 1 2 19.066V4.934a.5.5 0 0 1 .777-.416L12 10.667V4.934a.5.5 0 0 1 .777-.416l10.599 7.066a.5.5 0 0 1 0 .832l-10.599 7.066a.5.5 0 0 1-.777-.416zM10.394 12L4 7.737v8.526zM14 7.737v8.526L20.394 12z" />
</svg>

After

Width:  |  Height:  |  Size: 343 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M10 7.22L6.603 10H3v4h3.603L10 16.78zM5.889 16H2a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1h3.889l5.294-4.332a.5.5 0 0 1 .817.387v15.89a.5.5 0 0 1-.817.387zm14.525-4l3.536 3.536l-1.415 1.414L19 13.414l-3.536 3.536l-1.414-1.414L17.586 12L14.05 8.465l1.414-1.415L19 10.586l3.535-3.536l1.415 1.415z"/></svg>

After

Width:  |  Height:  |  Size: 395 B

+10
View File
@@ -18,6 +18,7 @@
<file>icons/black/svg/close-line.svg</file>
<file>icons/black/svg/controller-line.svg</file>
<file>icons/black/svg/controller-strike-line.svg</file>
<file>icons/black/svg/dashboard-line.svg</file>
<file>icons/black/svg/debug-step-into-line.svg</file>
<file>icons/black/svg/debug-step-out-line.svg</file>
<file>icons/black/svg/debug-step-over-line.svg</file>
@@ -32,6 +33,7 @@
<file>icons/black/svg/eject-line.svg</file>
<file>icons/black/svg/emulation-line.svg</file>
<file>icons/black/svg/eyetoy-line.svg</file>
<file>icons/black/svg/fast-forward-line.svg</file>
<file>icons/black/svg/file-add-line.svg</file>
<file>icons/black/svg/file-line.svg</file>
<file>icons/black/svg/file-list-line.svg</file>
@@ -90,6 +92,8 @@
<file>icons/black/svg/settings-3-line.svg</file>
<file>icons/black/svg/shut-down-line.svg</file>
<file>icons/black/svg/singstar-line.svg</file>
<file>icons/black/svg/slow-mo.svg</file>
<file>icons/black/svg/speed-line.svg</file>
<file>icons/black/svg/tools-line.svg</file>
<file>icons/black/svg/train-line.svg</file>
<file>icons/black/svg/trance-vibrator-line.svg</file>
@@ -97,6 +101,7 @@
<file>icons/black/svg/trophy-line.svg</file>
<file>icons/black/svg/tv-2-line.svg</file>
<file>icons/black/svg/usb-fill.svg</file>
<file>icons/black/svg/volume-mute-line.svg</file>
<file>icons/black/svg/volume-up-line.svg</file>
<file>icons/black/svg/warning-line.svg</file>
<file>icons/black/svg/wheel-line.svg</file>
@@ -123,6 +128,7 @@
<file>icons/white/svg/close-line.svg</file>
<file>icons/white/svg/controller-line.svg</file>
<file>icons/white/svg/controller-strike-line.svg</file>
<file>icons/white/svg/dashboard-line.svg</file>
<file>icons/white/svg/debug-step-into-line.svg</file>
<file>icons/white/svg/debug-step-out-line.svg</file>
<file>icons/white/svg/debug-step-over-line.svg</file>
@@ -137,6 +143,7 @@
<file>icons/white/svg/eject-line.svg</file>
<file>icons/white/svg/emulation-line.svg</file>
<file>icons/white/svg/eyetoy-line.svg</file>
<file>icons/white/svg/fast-forward-line.svg</file>
<file>icons/white/svg/file-add-line.svg</file>
<file>icons/white/svg/file-line.svg</file>
<file>icons/white/svg/file-list-line.svg</file>
@@ -198,6 +205,8 @@
<file>icons/white/svg/settings-3-line.svg</file>
<file>icons/white/svg/shut-down-line.svg</file>
<file>icons/white/svg/singstar-line.svg</file>
<file>icons/white/svg/slow-mo.svg</file>
<file>icons/white/svg/speed-line.svg</file>
<file>icons/white/svg/tools-line.svg</file>
<file>icons/white/svg/train-line.svg</file>
<file>icons/white/svg/trance-vibrator-line.svg</file>
@@ -205,6 +214,7 @@
<file>icons/white/svg/trophy-line.svg</file>
<file>icons/white/svg/tv-2-line.svg</file>
<file>icons/white/svg/usb-fill.svg</file>
<file>icons/white/svg/volume-mute-line.svg</file>
<file>icons/white/svg/volume-up-line.svg</file>
<file>icons/white/svg/warning-line.svg</file>
<file>icons/white/svg/wheel-line.svg</file>
+296 -159
View File
@@ -183,6 +183,8 @@ namespace Achievements
static void DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& entry, bool is_self, float rank_column_width,
float name_column_width, float time_column_width, float column_spacing);
static void OpenSubset(const rc_client_subset_t* subset);
static bool DrawSubsetSidebar(float sidebar_width, bool& sidebar_has_focus, bool leaderboards_only);
static void DrawSubsetSidebarFooter(bool sidebar_has_focus);
static void OpenLeaderboard(const rc_client_leaderboard_t* lboard);
static void LeaderboardFetchNearbyCallback(
int result, const char* error_message, rc_client_leaderboard_entry_list_t* list, rc_client_t* client, void* callback_userdata);
@@ -237,6 +239,9 @@ namespace Achievements
static float s_leaderboard_list_scroll = 0.0f;
static bool s_restore_leaderboard_scroll = false;
static std::map<std::pair<u32, u32>, bool> s_achievement_buckets_collapsed;
static std::map<std::pair<u32, u8>, bool> s_leaderboard_buckets_collapsed;
static std::vector<LeaderboardTrackerIndicator> s_active_leaderboard_trackers;
static std::vector<AchievementChallengeIndicator> s_active_challenge_indicators;
static std::optional<AchievementProgressIndicator> s_active_progress_indicator;
@@ -934,6 +939,17 @@ void Achievements::UpdateRichPresence(std::unique_lock<std::recursive_mutex>& lo
lock.lock();
}
void Achievements::PlayAchievementSound(bool is_specific_sound_enabled, const std::string& custom_sound_name, const std::string& default_sound_name)
{
if (!EmuConfig.Achievements.SoundEffects || !is_specific_sound_enabled)
return;
if (custom_sound_name.empty() || !FileSystem::FileExists(custom_sound_name.c_str()))
Common::PlaySoundAsync(Path::Combine(EmuFolders::Resources, default_sound_name).c_str());
else
Common::PlaySoundAsync(custom_sound_name.c_str());
}
void Achievements::GameChanged(u32 disc_crc, u32 crc)
{
std::unique_lock lock(s_achievements_mutex);
@@ -1151,13 +1167,7 @@ void Achievements::DisplayAchievementSummary()
}
});
}
if (EmuConfig.Achievements.SoundEffects && EmuConfig.Achievements.InfoSound)
Common::PlaySoundAsync(
(EmuConfig.Achievements.InfoSoundName.empty()
? Path::Combine(EmuFolders::Resources, DEFAULT_INFO_SOUND_NAME)
: EmuConfig.Achievements.InfoSoundName).c_str()
);
Achievements::PlayAchievementSound(EmuConfig.Achievements.InfoSound, EmuConfig.Achievements.InfoSoundName, DEFAULT_INFO_SOUND_NAME);
}
void Achievements::DisplayHardcoreDeferredMessage()
@@ -1207,13 +1217,7 @@ void Achievements::HandleUnlockEvent(const rc_client_event_t* event)
std::move(title), std::move(summary), std::move(badge_path));
});
}
if (EmuConfig.Achievements.SoundEffects && EmuConfig.Achievements.UnlockSound)
Common::PlaySoundAsync(
(EmuConfig.Achievements.UnlockSoundName.empty()
? Path::Combine(EmuFolders::Resources, DEFAULT_UNLOCK_SOUND_NAME)
: EmuConfig.Achievements.UnlockSoundName).c_str()
);
Achievements::PlayAchievementSound(EmuConfig.Achievements.UnlockSound, EmuConfig.Achievements.UnlockSoundName, DEFAULT_UNLOCK_SOUND_NAME);
}
void Achievements::HandleGameCompleteEvent(const rc_client_event_t* event)
@@ -1332,13 +1336,7 @@ void Achievements::HandleLeaderboardSubmittedEvent(const rc_client_event_t* even
}
});
}
if (EmuConfig.Achievements.SoundEffects && EmuConfig.Achievements.LBSubmitSound)
Common::PlaySoundAsync(
(EmuConfig.Achievements.LBSubmitSoundName.empty()
? Path::Combine(EmuFolders::Resources, DEFAULT_LBSUBMIT_SOUND_NAME)
: EmuConfig.Achievements.LBSubmitSoundName).c_str()
);
Achievements::PlayAchievementSound(EmuConfig.Achievements.LBSubmitSound, EmuConfig.Achievements.LBSubmitSoundName, DEFAULT_LBSUBMIT_SOUND_NAME);
}
void Achievements::HandleLeaderboardScoreboardEvent(const rc_client_event_t* event)
@@ -2032,6 +2030,9 @@ void Achievements::ClearUIState()
rc_client_destroy_achievement_list(s_achievement_list);
s_achievement_list = nullptr;
}
s_achievement_buckets_collapsed.clear();
s_leaderboard_buckets_collapsed.clear();
}
template <typename T>
@@ -2419,6 +2420,9 @@ bool Achievements::PrepareAchievementsWindow()
CloseSubset();
s_subset_list = rc_client_create_subset_list(s_client);
if (!s_subset_list)
Console.Warning("Achievements: rc_client_create_subset_list() returned null");
s_achievement_buckets_collapsed.clear();
if (s_achievement_list)
rc_client_destroy_achievement_list(s_achievement_list);
@@ -2433,6 +2437,131 @@ bool Achievements::PrepareAchievementsWindow()
return true;
}
bool Achievements::DrawSubsetSidebar(float sidebar_width, bool& sidebar_has_focus, bool leaderboards_only)
{
if (!s_subset_list)
return false;
using ImGuiFullscreen::g_medium_font;
using ImGuiFullscreen::LayoutScale;
static constexpr float alpha = 0.8f;
const ImVec4 sidebar_bg(0.10f, 0.10f, 0.10f, alpha);
ImGui::PushStyleColor(ImGuiCol_ChildBg, sidebar_bg);
ImGui::PushStyleColor(ImGuiCol_NavCursor, IM_COL32(0, 0, 0, 0));
bool subset_selected = false;
if (ImGui::BeginChild("subset_sidebar", ImVec2(sidebar_width, 0.0f), 0, 0))
{
sidebar_has_focus = ImGui::IsWindowFocused();
ImGuiFullscreen::BeginMenuButtons();
const float padding = LayoutScale(10.0f);
const float spacing = LayoutScale(8.0f);
const float icon_size = LayoutScale(28.0f);
for (u32 i = 0; i < s_subset_list->num_subsets; i++)
{
const rc_client_subset_t* subset = s_subset_list->subsets[i];
if (leaderboards_only && subset->num_leaderboards == 0)
continue;
TinyString id_str;
id_str.format("sidebar_subset_{}", subset->id);
ImRect bb;
bool visible, hovered;
ImGuiFullscreen::MenuButtonFrame(id_str, true, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY,
&visible, &hovered, &bb.Min, &bb.Max, 0, 1.0f);
if (s_open_subset && s_open_subset->id == subset->id)
{
ImGui::GetWindowDrawList()->AddRectFilled(
ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x + LayoutScale(4.0f), bb.Max.y), IM_COL32(52, 152, 219, 255));
}
const ImVec2 icon_min(bb.Min.x + padding, bb.Min.y + (bb.Max.y - bb.Min.y - icon_size) * 0.5f);
const ImVec2 icon_max(icon_min.x + icon_size, icon_min.y + icon_size);
std::string badge_path = GetSubsetBadgePath(subset);
if (!badge_path.empty())
{
GSTexture* badge = ImGuiFullscreen::GetCachedTextureAsync(badge_path);
if (badge)
{
ImGui::GetWindowDrawList()->AddImage(reinterpret_cast<ImTextureID>(badge->GetNativeHandle()),
icon_min, icon_max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255));
}
}
const ImRect text_bb(ImVec2(icon_max.x + spacing, bb.Min.y), ImVec2(bb.Max.x - padding, bb.Max.y));
ImGui::PushFont(g_medium_font.first, g_medium_font.second);
ImGuiFullscreen::RenderTextClippedWithShadow(
text_bb.Min, text_bb.Max, subset->title, nullptr, nullptr, ImVec2(0.0f, 0.5f), &text_bb);
ImGui::PopFont();
if (ImGui::IsItemClicked() || ImGui::IsItemActivated())
{
OpenSubset(subset);
subset_selected = true;
break;
}
}
ImGuiFullscreen::EndMenuButtons();
}
ImGui::EndChild();
ImGui::PopStyleColor(2);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
ImGui::SameLine();
ImGui::PopStyleVar();
return subset_selected;
}
void Achievements::DrawSubsetSidebarFooter(bool sidebar_has_focus)
{
if (ImGuiFullscreen::IsGamepadInputSource())
{
const bool circleOK = ImGui::GetIO().ConfigNavSwapGamepadButtons;
const auto glyphs = ImGuiFullscreen::GetGamepadGlyphs();
if (sidebar_has_focus)
{
ImGuiFullscreen::SetFullscreenFooterText(std::array{
std::make_pair(glyphs.dpad_ud, TRANSLATE_SV("Achievements", "Navigate Subsets")),
std::make_pair(glyphs.confirm(circleOK), TRANSLATE_SV("Achievements", "Select")),
std::make_pair(glyphs.cancel(circleOK), TRANSLATE_SV("Achievements", "Back")),
});
}
else
{
ImGuiFullscreen::SetFullscreenFooterText(std::array{
std::make_pair(glyphs.dpad_ud, TRANSLATE_SV("Achievements", "Change Selection")),
std::make_pair(glyphs.cancel(circleOK), TRANSLATE_SV("Achievements", "Subsets")),
});
}
}
else
{
if (sidebar_has_focus)
{
ImGuiFullscreen::SetFullscreenFooterText(std::array{
std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN, TRANSLATE_SV("Achievements", "Navigate Subsets")),
std::make_pair(ICON_PF_ENTER, TRANSLATE_SV("Achievements", "Select")),
std::make_pair(ICON_PF_ESC, TRANSLATE_SV("Achievements", "Back")),
});
}
else
{
ImGuiFullscreen::SetFullscreenFooterText(std::array{
std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN, TRANSLATE_SV("Achievements", "Change Selection")),
std::make_pair(ICON_PF_ESC, TRANSLATE_SV("Achievements", "Subsets")),
});
}
}
}
void Achievements::DrawAchievementsWindow()
{
using ImGuiFullscreen::g_large_font;
@@ -2449,7 +2578,10 @@ void Achievements::DrawAchievementsWindow()
Achievements::IdleUpdate();
const bool has_multiple_subsets = (s_subset_list && s_subset_list->num_subsets > 1);
bool s_sidebar_has_focus = false;
static bool sidebar_has_focus = false;
static bool focus_sidebar = false;
if (!has_multiple_subsets)
sidebar_has_focus = false;
static constexpr float alpha = 0.8f;
static constexpr float heading_alpha = 0.95f;
@@ -2493,11 +2625,18 @@ void Achievements::DrawAchievementsWindow()
SmallString text;
ImVec2 text_size;
const bool wants_close = ImGuiFullscreen::WantsToCloseMenu();
if (ImGuiFullscreen::FloatingButton(ICON_FA_SQUARE_XMARK, 10.0f, 10.0f, -1.0f, -1.0f, 1.0f, 0.0f, true, g_large_font) ||
ImGuiFullscreen::WantsToCloseMenu())
(wants_close && (!has_multiple_subsets || sidebar_has_focus)))
{
sidebar_has_focus = false;
FullscreenUI::ReturnToPreviousWindow();
}
else if (has_multiple_subsets && !sidebar_has_focus && wants_close)
{
sidebar_has_focus = true;
focus_sidebar = true;
}
const ImRect title_bb(ImVec2(left, top), ImVec2(right, top + GetLineHeight(g_large_font)));
text.assign(s_open_subset ? s_open_subset->title : s_game_title);
@@ -2589,75 +2728,13 @@ void Achievements::DrawAchievementsWindow()
{
if (has_multiple_subsets)
{
const ImVec4 sidebar_bg(0.10f, 0.10f, 0.10f, alpha);
ImGui::PushStyleColor(ImGuiCol_ChildBg, sidebar_bg);
if (ImGui::BeginChild("subset_sidebar", ImVec2(sidebar_width, 0.0f), ImGuiChildFlags_NavFlattened, 0))
if (focus_sidebar)
{
ImGuiFullscreen::BeginMenuButtons();
for (u32 i = 0; i < s_subset_list->num_subsets; i++)
{
const rc_client_subset_t* subset = s_subset_list->subsets[i];
TinyString id_str;
id_str.format("sidebar_subset_{}", subset->id);
ImRect bb;
bool visible, hovered;
ImGuiFullscreen::MenuButtonFrame(id_str, true, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY,
&visible, &hovered, &bb.Min, &bb.Max, 0, 1.0f);
if (ImGui::IsItemHovered() || ImGui::IsItemFocused())
s_sidebar_has_focus = true;
const float padding = LayoutScale(10.0f);
const float spacing = LayoutScale(8.0f);
const float icon_size = LayoutScale(28.0f);
if (s_open_subset && s_open_subset->id == subset->id)
{
const float line_width = LayoutScale(4.0f);
const ImVec2 line_min(bb.Min.x, bb.Min.y);
const ImVec2 line_max(bb.Min.x + line_width, bb.Max.y);
ImGui::GetWindowDrawList()->AddRectFilled(line_min, line_max, IM_COL32(52, 152, 219, 255));
}
const ImVec2 icon_min(bb.Min.x + padding, bb.Min.y + (bb.Max.y - bb.Min.y - icon_size) * 0.5f);
const ImVec2 icon_max(icon_min.x + icon_size, icon_min.y + icon_size);
std::string badge_path = GetSubsetBadgePath(subset);
if (!badge_path.empty())
{
GSTexture* badge = ImGuiFullscreen::GetCachedTextureAsync(badge_path);
if (badge)
{
ImGui::GetWindowDrawList()->AddImage(reinterpret_cast<ImTextureID>(badge->GetNativeHandle()),
icon_min, icon_max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255));
}
}
const float text_x = icon_max.x + spacing;
const ImRect text_bb(ImVec2(text_x, bb.Min.y), ImVec2(bb.Max.x - padding, bb.Max.y));
ImGui::PushFont(g_medium_font.first, g_medium_font.second);
ImGuiFullscreen::RenderTextClippedWithShadow(
text_bb.Min, text_bb.Max, subset->title, nullptr, nullptr, ImVec2(0.0f, 0.5f), &text_bb);
ImGui::PopFont();
if (ImGui::IsItemClicked() || ImGui::IsItemActivated())
OpenSubset(subset);
}
ImGuiFullscreen::EndMenuButtons();
ImGui::SetNextWindowFocus();
focus_sidebar = false;
}
ImGui::EndChild();
if (ImGui::IsItemHovered() || ImGui::IsItemFocused())
s_sidebar_has_focus = true;
ImGui::PopStyleColor();
ImGui::SameLine();
if (DrawSubsetSidebar(sidebar_width, sidebar_has_focus, false))
ImGui::SetNextWindowFocus();
}
const float content_width = has_multiple_subsets ? (display_size.x - sidebar_width) : display_size.x;
@@ -2667,12 +2744,9 @@ void Achievements::DrawAchievementsWindow()
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImGui::GetColorU32(ImGuiFullscreen::UIPrimaryColor));
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImGui::GetColorU32(ImGuiFullscreen::UISecondaryColor));
if (ImGui::BeginChild("achievements_content", ImVec2(content_width, 0.0f), ImGuiChildFlags_NavFlattened, 0))
if (ImGui::BeginChild("achievements_content", ImVec2(content_width, 0.0f), 0, 0))
{
if (ImGui::IsWindowHovered() || ImGui::IsWindowFocused())
s_sidebar_has_focus = false;
static std::map<std::pair<u32, u32>, bool> buckets_collapsed;
static const char* bucket_names[NUM_RC_CLIENT_ACHIEVEMENT_BUCKETS] = {
TRANSLATE_NOOP("Achievements", "Unknown"),
TRANSLATE_NOOP("Achievements", "Locked"),
@@ -2701,7 +2775,7 @@ void Achievements::DrawAchievementsWindow()
pxAssert(bucket.bucket_type < NUM_RC_CLIENT_ACHIEVEMENT_BUCKETS);
bool& bucket_collapsed = buckets_collapsed[std::make_pair(bucket.subset_id, bucket.bucket_type)];
bool& bucket_collapsed = s_achievement_buckets_collapsed[std::make_pair(bucket.subset_id, bucket.bucket_type)];
const char* translated_bucket_name = Host::TranslateToCString("Achievements", bucket_names[bucket.bucket_type]);
bucket_collapsed ^=
@@ -2726,54 +2800,9 @@ void Achievements::DrawAchievementsWindow()
ImGuiFullscreen::EndFullscreenWindow();
if (has_multiple_subsets)
{
if (ImGuiFullscreen::IsGamepadInputSource())
{
const bool circleOK = ImGui::GetIO().ConfigNavSwapGamepadButtons;
const auto glyphs = ImGuiFullscreen::GetGamepadGlyphs();
if (s_sidebar_has_focus)
{
ImGuiFullscreen::SetFullscreenFooterText(std::array{
std::make_pair(glyphs.dpad_ud, TRANSLATE_SV("Achievements", "Navigate Subsets")),
std::make_pair(glyphs.dpad_lr, TRANSLATE_SV("Achievements", "Back to List")),
std::make_pair(glyphs.confirm(circleOK), TRANSLATE_SV("Achievements", "Select")),
std::make_pair(glyphs.cancel(circleOK), TRANSLATE_SV("Achievements", "Back")),
});
}
else
{
ImGuiFullscreen::SetFullscreenFooterText(std::array{
std::make_pair(glyphs.dpad_lr, TRANSLATE_SV("Achievements", "Navigate Subsets")),
std::make_pair(glyphs.dpad_ud, TRANSLATE_SV("Achievements", "Change Selection")),
std::make_pair(glyphs.cancel(circleOK), TRANSLATE_SV("Achievements", "Back")),
});
}
}
else
{
if (s_sidebar_has_focus)
{
ImGuiFullscreen::SetFullscreenFooterText(std::array{
std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN, TRANSLATE_SV("Achievements", "Navigate Subsets")),
std::make_pair(ICON_PF_ARROW_LEFT ICON_PF_ARROW_RIGHT, TRANSLATE_SV("Achievements", "Back to List")),
std::make_pair(ICON_PF_ENTER, TRANSLATE_SV("Achievements", "Select")),
std::make_pair(ICON_PF_ESC, TRANSLATE_SV("Achievements", "Back")),
});
}
else
{
ImGuiFullscreen::SetFullscreenFooterText(std::array{
std::make_pair(ICON_PF_ARROW_LEFT ICON_PF_ARROW_RIGHT, TRANSLATE_SV("Achievements", "Navigate Subsets")),
std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN, TRANSLATE_SV("Achievements", "Change Selection")),
std::make_pair(ICON_PF_ESC, TRANSLATE_SV("Achievements", "Back")),
});
}
}
}
DrawSubsetSidebarFooter(sidebar_has_focus);
else
{
FullscreenUI::SetStandardSelectionFooterText(true);
}
}
void Achievements::DrawAchievement(const rc_client_achievement_t* cheevo)
@@ -2989,9 +3018,21 @@ bool Achievements::PrepareLeaderboardsWindow()
s_achievement_badge_paths = {};
CloseLeaderboard();
if (s_subset_list)
{
rc_client_destroy_subset_list(s_subset_list);
s_subset_list = nullptr;
}
CloseSubset();
s_subset_list = rc_client_create_subset_list(client);
if (!s_subset_list)
Console.Warning("Achievements: rc_client_create_subset_list() returned null");
s_leaderboard_buckets_collapsed.clear();
if (s_leaderboard_list)
rc_client_destroy_leaderboard_list(s_leaderboard_list);
s_leaderboard_list = rc_client_create_leaderboard_list(client, RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE);
s_leaderboard_list = rc_client_create_leaderboard_list(client, RC_CLIENT_LEADERBOARD_LIST_GROUPING_TRACKING);
if (!s_leaderboard_list)
{
Console.Error("Achievements: rc_client_create_leaderboard_list() returned null");
@@ -3004,6 +3045,18 @@ bool Achievements::PrepareLeaderboardsWindow()
s_restore_leaderboard_scroll = false;
s_leaderboard_list_scroll = 0.0f;
if (s_subset_list)
{
for (u32 i = 0; i < s_subset_list->num_subsets; i++)
{
if (s_subset_list->subsets[i]->num_leaderboards > 0)
{
OpenSubset(s_subset_list->subsets[i]);
break;
}
}
}
return true;
}
@@ -3027,6 +3080,14 @@ void Achievements::DrawLeaderboardsWindow()
const bool is_leaderboard_open = (s_open_leaderboard != nullptr);
bool close_leaderboard_on_exit = false;
const bool has_multiple_subsets = (s_subset_list && std::count_if(s_subset_list->subsets,
s_subset_list->subsets + s_subset_list->num_subsets,
[](const rc_client_subset_t* subset) { return subset->num_leaderboards > 0; }) > 1);
static bool sidebar_has_focus = false;
static bool focus_sidebar = false;
if (!has_multiple_subsets || is_leaderboard_open)
sidebar_has_focus = false;
ImRect bb;
const ImVec4 background(0.13f, 0.13f, 0.13f, alpha);
@@ -3089,11 +3150,18 @@ void Achievements::DrawLeaderboardsWindow()
if (!is_leaderboard_open)
{
const bool wants_close = ImGuiFullscreen::WantsToCloseMenu();
if (ImGuiFullscreen::FloatingButton(ICON_FA_SQUARE_XMARK, 10.0f, 10.0f, -1.0f, -1.0f, 1.0f, 0.0f, true, g_large_font) ||
ImGuiFullscreen::WantsToCloseMenu())
(wants_close && (!has_multiple_subsets || sidebar_has_focus)))
{
sidebar_has_focus = false;
FullscreenUI::ReturnToPreviousWindow();
}
else if (has_multiple_subsets && !sidebar_has_focus && wants_close)
{
sidebar_has_focus = true;
focus_sidebar = true;
}
}
else
{
@@ -3250,36 +3318,105 @@ void Achievements::DrawLeaderboardsWindow()
if (!is_leaderboard_open)
{
const float sidebar_width = has_multiple_subsets ? LayoutScale(200.0f) : 0.0f;
if (ImGuiFullscreen::BeginFullscreenWindow(
ImVec2(0.0f, heading_height),
ImVec2(display_size.x, display_size.y - heading_height - LayoutScale(ImGuiFullscreen::LAYOUT_FOOTER_HEIGHT)),
"leaderboards", background, 0.0f, ImVec2(ImGuiFullscreen::LAYOUT_MENU_WINDOW_X_PADDING, 0.0f), 0))
"leaderboards", background, 0.0f, ImVec2(0.0f, 0.0f), 0))
{
if (s_restore_leaderboard_scroll)
if (has_multiple_subsets)
{
ImGui::SetScrollY(s_leaderboard_list_scroll);
s_restore_leaderboard_scroll = false;
if (focus_sidebar)
{
ImGui::SetNextWindowFocus();
focus_sidebar = false;
}
if (DrawSubsetSidebar(sidebar_width, sidebar_has_focus, true))
ImGui::SetNextWindowFocus();
}
const bool had_pending_focus_request = s_restore_leaderboard_focus;
const float content_width = has_multiple_subsets ? (display_size.x - sidebar_width) : display_size.x;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(ImGuiFullscreen::LAYOUT_MENU_WINDOW_X_PADDING, 0.0f));
ImGui::PushStyleColor(ImGuiCol_ChildBg, background);
ImGui::PushStyleColor(ImGuiCol_Header, ImGui::GetColorU32(ImGuiFullscreen::UIPrimaryDarkColor));
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImGui::GetColorU32(ImGuiFullscreen::UIPrimaryColor));
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImGui::GetColorU32(ImGuiFullscreen::UISecondaryColor));
ImGuiFullscreen::BeginMenuButtons();
for (u32 bucket_index = 0; bucket_index < s_leaderboard_list->num_buckets; bucket_index++)
if (ImGui::BeginChild("leaderboards_content", ImVec2(content_width, 0.0f), 0, 0))
{
const rc_client_leaderboard_bucket_t& bucket = s_leaderboard_list->buckets[bucket_index];
for (u32 i = 0; i < bucket.num_leaderboards; i++)
DrawLeaderboardListEntry(bucket.leaderboards[i]);
if (s_restore_leaderboard_scroll)
{
ImGui::SetScrollY(s_leaderboard_list_scroll);
s_restore_leaderboard_scroll = false;
}
const bool had_pending_focus_request = s_restore_leaderboard_focus;
ImGuiFullscreen::BeginMenuButtons();
static const char* lboard_bucket_names[NUM_RC_CLIENT_LEADERBOARD_BUCKETS] = {
TRANSLATE_NOOP("Achievements", "Unknown"),
TRANSLATE_NOOP("Achievements", "Inactive"),
TRANSLATE_NOOP("Achievements", "Active"),
TRANSLATE_NOOP("Achievements", "Unsupported"),
TRANSLATE_NOOP("Achievements", "All"),
};
const bool show_bucket_headers = (s_leaderboard_list->num_buckets > 1);
for (u32 bucket_index = 0; bucket_index < s_leaderboard_list->num_buckets; bucket_index++)
{
const rc_client_leaderboard_bucket_t& bucket = s_leaderboard_list->buckets[bucket_index];
if (s_open_subset && bucket.subset_id != 0 && bucket.subset_id != s_open_subset->id)
continue;
if (show_bucket_headers)
{
pxAssert(bucket.bucket_type < NUM_RC_CLIENT_LEADERBOARD_BUCKETS);
const char* type_name = Host::TranslateToCString("Achievements", lboard_bucket_names[bucket.bucket_type]);
TinyString bucket_label;
if (bucket.subset_id != 0 && !s_open_subset && s_subset_list)
{
auto subset_iter = std::find_if(s_subset_list->subsets, s_subset_list->subsets + s_subset_list->num_subsets,
[&](const rc_client_subset_t* s) { return s->id == bucket.subset_id; });
if (subset_iter != s_subset_list->subsets + s_subset_list->num_subsets)
bucket_label.format("{} - {}", (*subset_iter)->title, type_name);
else
bucket_label.assign(type_name);
}
else
{
bucket_label.assign(type_name);
}
bool& bucket_collapsed = s_leaderboard_buckets_collapsed[std::make_pair(bucket.subset_id, bucket.bucket_type)];
if (ImGuiFullscreen::MenuHeadingButton(
bucket_label, bucket_collapsed ? ICON_FA_CHEVRON_DOWN : ICON_FA_CHEVRON_UP))
bucket_collapsed = !bucket_collapsed;
if (bucket_collapsed)
continue;
}
for (u32 i = 0; i < bucket.num_leaderboards; i++)
DrawLeaderboardListEntry(bucket.leaderboards[i]);
}
ImGuiFullscreen::EndMenuButtons();
if (had_pending_focus_request && s_restore_leaderboard_focus)
s_restore_leaderboard_focus = false;
}
ImGui::EndChild();
ImGuiFullscreen::EndMenuButtons();
if (had_pending_focus_request && s_restore_leaderboard_focus)
s_restore_leaderboard_focus = false;
ImGui::PopStyleColor(4);
ImGui::PopStyleVar();
}
ImGuiFullscreen::EndFullscreenWindow();
FullscreenUI::SetStandardSelectionFooterText(true);
if (has_multiple_subsets)
DrawSubsetSidebarFooter(sidebar_has_focus);
else
FullscreenUI::SetStandardSelectionFooterText(true);
}
else
{
+3
View File
@@ -67,6 +67,9 @@ namespace Achievements
/// Called when the system changes game, or is booting.
void GameChanged(u32 disc_crc, u32 crc);
/// Play achievement related sounds effects
void PlayAchievementSound(bool is_specific_sound_enabled, const std::string& custom_sound_name, const std::string& default_sound_name);
/// Re-enables hardcode mode if it is enabled in the settings.
bool ResetHardcoreMode(bool is_booting);
+4 -3
View File
@@ -161,7 +161,8 @@ static bool OpenGSDevice(GSRendererType renderer, bool clear_state_on_fail, bool
return false;
}
GSConfig.OsdShowGPU = GSConfig.OsdShowGPU && g_gs_device->SetGPUTimingEnabled(true);
if (!g_gs_device->SetGPUTimingEnabled(true))
GSConfig.OsdShowGPU = false;
Console.WriteLn(Color_StrongGreen, "%s Graphics Driver Info:", GSDevice::RenderAPIToString(new_api));
Console.WriteLn(g_gs_device->GetDriverInfo());
@@ -878,9 +879,9 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config)
g_gs_renderer->PurgeTextureCache(true, false, true);
}
if (GSConfig.OsdShowGPU != old_config.OsdShowGPU)
if (GSConfig.OsdShowGPU && !old_config.OsdShowGPU)
{
if (!g_gs_device->SetGPUTimingEnabled(GSConfig.OsdShowGPU))
if (!g_gs_device->SetGPUTimingEnabled(true))
GSConfig.OsdShowGPU = false;
}
}
+1 -2
View File
@@ -700,8 +700,7 @@ void GSRenderer::VSync(u32 field, bool registers_written, bool idle_frame)
EndPresentFrame();
if (GSConfig.OsdShowGPU || GSDumpReplayer::IsReplayingDump())
PerformanceMetrics::OnGPUPresent(g_gs_device->GetAndResetAccumulatedGPUTime());
PerformanceMetrics::OnGPUPresent(g_gs_device->GetAndResetAccumulatedGPUTime());
}
PerformanceMetrics::Update(registers_written, fb_sprite_frame, false);
+1 -1
View File
@@ -20,7 +20,7 @@
#include <array>
#include <d3d11.h>
#include <d3d12.h>
#include <directx/d3d12.h>
#include <d3dcompiler.h>
#include <fstream>
+1 -1
View File
@@ -8,7 +8,7 @@
#include "common/RedtapeWilCom.h"
#include <array>
#include <d3d12.h>
#include <directx/d3d12.h>
#include <string_view>
class D3D12ShaderCache;
@@ -10,7 +10,7 @@
#include <bitset>
#include <cstring>
#include <d3d12.h>
#include <directx/d3d12.h>
#include <unordered_map>
#include <vector>
+1 -1
View File
@@ -10,7 +10,7 @@
#include "common/RedtapeWilCom.h"
#include <cstdio>
#include <d3d12.h>
#include <directx/d3d12.h>
#include <string_view>
#include <unordered_map>
#include <vector>
+1 -1
View File
@@ -8,7 +8,7 @@
#include "common/RedtapeWilCom.h"
#include "common/Assertions.h"
#include <d3d12.h>
#include <directx/d3d12.h>
#include <deque>
#include <utility>
+1
View File
@@ -1442,6 +1442,7 @@ bool GSDevice12::CheckFeatures(const u32& vendor_id)
m_device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &options, sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS));
m_features.rov = options.ROVsSupported;
Console.WriteLnFmt("D3D12: Tight Alignment: {}", m_allocator->IsTightAlignmentSupported() ? "Supported" : "Not Supported");
return true;
}
-497
View File
@@ -3864,273 +3864,6 @@ bool FullscreenUI::OpenAchievementsWindow()
return true;
}
void FullscreenUI::DrawAchievementsLoginWindow()
{
if (s_achievements_login_open && !ImGui::IsPopupOpen("RetroAchievements"))
ImGui::OpenPopup("RetroAchievements");
const float dialog_width = std::clamp(LayoutScale(420.0f), 300.0f, ImGui::GetIO().DisplaySize.x);
ImGui::SetNextWindowSizeConstraints(ImVec2(dialog_width, 0.0f), ImVec2(dialog_width, ImGui::GetIO().DisplaySize.y));
ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(ImGuiFullscreen::LAYOUT_WINDOW_ROUNDING));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(24.0f, 24.0f));
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.13f, 0.13f, 0.13f, 0.95f));
if (ImGui::BeginPopupModal("RetroAchievements", &s_achievements_login_open, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize))
{
const float content_width = ImGui::GetContentRegionAvail().x;
ImGui::PushFont(g_large_font.first, g_large_font.second);
const float icon_height = LayoutScale(24.0f);
const float icon_width = icon_height * (500.0f / 275.0f);
GSTexture* ra_icon = GetCachedSvgTextureAsync("icons/ra-icon.svg", ImVec2(icon_width, icon_height));
const float title_width = ImGui::CalcTextSize("RetroAchievements").x;
const float header_width = (ra_icon ? icon_width + LayoutScale(10.0f) : 0.0f) + title_width;
const float header_start = (content_width - header_width) * 0.5f;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + header_start);
if (ra_icon)
{
ImGui::Image(reinterpret_cast<ImTextureID>(ra_icon->GetNativeHandle()),
ImVec2(icon_width, icon_height));
ImGui::SameLine();
}
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(1.0f));
ImGui::TextUnformatted(FSUI_CSTR("RetroAchievements"));
ImGui::PopFont();
ImGui::Spacing();
ImGui::Spacing();
ImGui::PushTextWrapPos(content_width);
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.8f, 0.8f, 0.8f, 1.0f));
ImGui::TextWrapped("%s", FSUI_CSTR("Please enter your user name and password for retroachievements.org below.\n\nYour password will not be saved in PCSX2, an access token will be generated and used instead."));
ImGui::PopStyleColor();
ImGui::PopTextWrapPos();
ImGui::Spacing();
ImGui::Spacing();
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, LayoutScale(ImGuiFullscreen::LAYOUT_FRAME_ROUNDING));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, LayoutScale(12.0f, 10.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, LayoutScale(1.0f));
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.2f, 0.2f, 0.2f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.25f, 0.25f, 0.25f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.4f, 0.4f, 0.4f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
if (s_achievements_login_logging_in || s_achievements_login_show_dismiss)
ImGui::BeginDisabled();
ImGui::SetNextItemWidth(content_width);
ImGui::InputTextWithHint("##username", FSUI_CSTR("Username"), s_achievements_login_username, sizeof(s_achievements_login_username));
ImGui::Spacing();
ImGui::SetNextItemWidth(content_width);
ImGui::InputTextWithHint("##password", FSUI_CSTR("Password"), s_achievements_login_password, sizeof(s_achievements_login_password), ImGuiInputTextFlags_Password);
ImGui::PopStyleColor(5);
ImGui::PopStyleVar(3);
if (s_achievements_login_logging_in || s_achievements_login_show_dismiss)
ImGui::EndDisabled();
ImGui::Spacing();
ImGui::Spacing();
if (s_achievements_login_logging_in)
{
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f));
const float status_width = ImGui::CalcTextSize("Logging in...").x;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (content_width - status_width) * 0.5f);
ImGui::TextUnformatted(FSUI_CSTR("Logging in..."));
ImGui::PopStyleColor();
ImGui::Spacing();
}
else if (s_achievements_login_show_dismiss)
{
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.35f, 0.9f, 0.35f, 1.0f));
const TinyString username = Host::GetBaseTinyStringSettingValue("Achievements", "Username", "");
const SmallString success_text = SmallString::from_format(
FSUI_FSTR("Successfully logged in as {}."), username.empty() ? "Unknown" : username.view());
const float status_width = ImGui::CalcTextSize(success_text.c_str()).x;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (content_width - status_width) * 0.5f);
ImGui::TextUnformatted(success_text.c_str());
ImGui::PopStyleColor();
ImGui::Spacing();
}
const float button_height = std::max(LayoutScale(36.0f), 28.0f);
const float button_spacing = LayoutScale(12.0f);
const float button_width = (content_width - button_spacing) * 0.5f;
auto CloseLoginPopup = []() {
ImGui::CloseCurrentPopup();
s_achievements_login_open = false;
s_achievements_login_logging_in = false;
s_achievements_login_show_dismiss = false;
s_achievements_login_username[0] = '\0';
s_achievements_login_password[0] = '\0';
};
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, LayoutScale(ImGuiFullscreen::LAYOUT_FRAME_ROUNDING));
const bool can_login = !s_achievements_login_show_dismiss && !s_achievements_login_logging_in &&
strlen(s_achievements_login_username) > 0 &&
strlen(s_achievements_login_password) > 0;
if (s_achievements_login_show_dismiss)
{
// keep dialog open and let user explicitly dismiss.
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.5f, 0.9f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.6f, 1.0f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.1f, 0.4f, 0.8f, 1.0f));
if (ImGui::Button(FSUI_CSTR("Dismiss"), ImVec2(button_width, button_height)) && !s_achievements_login_logging_in)
CloseLoginPopup();
ImGui::PopStyleColor(3);
ImGui::SameLine(0, button_spacing);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
ImGui::BeginDisabled();
ImGui::Button(FSUI_CSTR("Login"), ImVec2(button_width, button_height));
ImGui::EndDisabled();
ImGui::PopStyleColor(3);
ImGui::PopStyleVar();
ImGui::EndPopup();
ImGui::PopStyleColor();
ImGui::PopStyleVar(2);
return;
}
if (can_login)
{
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.5f, 0.9f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.6f, 1.0f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.1f, 0.4f, 0.8f, 1.0f));
}
else
{
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
}
if (ImGui::Button(FSUI_CSTR("Login"), ImVec2(button_width, button_height)) && can_login)
{
s_achievements_login_logging_in = true;
s_achievements_login_show_dismiss = false;
Host::RunOnCPUThread([username = std::string(s_achievements_login_username),
password = std::string(s_achievements_login_password)]() {
Error error;
const bool result = Achievements::Login(username.c_str(), password.c_str(), &error);
s_achievements_login_logging_in = false;
if (!result)
{
ShowToast(std::string(), fmt::format(FSUI_FSTR("Login failed.\nError: {}\n\nPlease check your username and password, and try again."),
error.GetDescription()));
return;
}
s_achievements_login_password[0] = '\0';
if (s_achievements_login_reason == Achievements::LoginRequestReason::UserInitiated)
{
if (!Host::GetBaseBoolSettingValue("Achievements", "Enabled", false))
{
OpenConfirmMessageDialog(FSUI_STR("Enable Achievements"),
FSUI_STR("Achievement tracking is not currently enabled. Your login will have no effect until "
"after tracking is enabled.\n\nDo you want to enable tracking now?"),
[](bool result) {
if (result)
{
Host::SetBaseBoolSettingValue("Achievements", "Enabled", true);
Host::CommitBaseSettingChanges();
VMManager::ApplySettings();
}
});
}
if (!Host::GetBaseBoolSettingValue("Achievements", "ChallengeMode", false))
{
OpenConfirmMessageDialog(FSUI_STR("Enable Hardcore Mode"),
FSUI_STR("Hardcore mode is not currently enabled. Enabling hardcore mode allows you to set times, scores, and "
"participate in game-specific leaderboards.\n\nHowever, hardcore mode also prevents the usage of save "
"states, cheats and slowdown functionality.\n\nDo you want to enable hardcore mode?"),
[](bool result) {
if (result)
{
Host::SetBaseBoolSettingValue("Achievements", "ChallengeMode", true);
Host::CommitBaseSettingChanges();
VMManager::ApplySettings();
bool has_active_game;
{
auto lock = Achievements::GetLock();
has_active_game = Achievements::HasActiveGame();
}
if (has_active_game)
{
OpenConfirmMessageDialog(FSUI_STR("Reset System"),
FSUI_STR("Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now?"),
[](bool reset) {
if (reset && VMManager::HasValidVM())
RequestReset();
});
}
}
});
}
}
s_achievements_login_show_dismiss = true;
});
}
ImGui::PopStyleColor(3);
ImGui::SameLine(0, button_spacing);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.4f, 0.4f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.5f, 0.5f, 0.5f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
if (ImGui::Button(FSUI_CSTR("Cancel"), ImVec2(button_width, button_height)) && !s_achievements_login_logging_in)
{
if (s_achievements_login_reason == Achievements::LoginRequestReason::TokenInvalid)
{
if (VMManager::HasValidVM() && !Achievements::HasActiveGame())
Achievements::DisableHardcoreMode();
}
CloseLoginPopup();
}
ImGui::PopStyleColor(3);
ImGui::PopStyleVar();
ImGui::EndPopup();
}
ImGui::PopStyleColor();
ImGui::PopStyleVar(2);
}
bool FullscreenUI::IsAchievementsWindowOpen()
{
return (s_current_main_window == MainWindowType::Achievements);
@@ -4204,236 +3937,6 @@ void FullscreenUI::SwitchToLeaderboardsWindow()
QueueResetFocus(FocusResetType::WindowChanged);
}
void FullscreenUI::DrawAchievementsSettingsPage(std::unique_lock<std::mutex>& settings_lock)
{
#ifdef ENABLE_RAINTEGRATION
if (Achievements::IsUsingRAIntegration())
{
BeginMenuButtons();
ActiveButton(FSUI_ICONSTR(ICON_FA_BAN, "RAIntegration is being used instead of the built-in achievements implementation."), false,
false, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
EndMenuButtons();
return;
}
#endif
SettingsInterface* bsi = GetEditingSettingsInterface();
bool check_challenge_state = false;
BeginMenuButtons();
MenuHeading(FSUI_CSTR("Settings"));
check_challenge_state = DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_TROPHY, "Enable Achievements"),
FSUI_CSTR("When enabled and logged in, PCSX2 will scan for achievements on startup."), "Achievements", "Enabled", false);
const bool enabled = bsi->GetBoolValue("Achievements", "Enabled", false);
check_challenge_state |= DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_PF_DUMBELL, "Hardcore Mode"),
FSUI_CSTR(
"\"Challenge\" mode for achievements, including leaderboard tracking. Disables save state, cheats, and slowdown functions."),
"Achievements", "ChallengeMode", false, enabled);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_BELL, "Achievement Notifications"),
FSUI_CSTR("Displays popup messages on events such as achievement unlocks and leaderboard submissions."), "Achievements",
"Notifications", true, enabled);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_LIST_OL, "Leaderboard Notifications"),
FSUI_CSTR("Displays popup messages when starting, submitting, or failing a leaderboard challenge."), "Achievements",
"LeaderboardNotifications", true, enabled);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_HEADPHONES, "Sound Effects"),
FSUI_CSTR("Plays sound effects for events such as achievement unlocks and leaderboard submissions."), "Achievements",
"SoundEffects", true, enabled);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_PF_HEARTBEAT_ALT, "Enable In-Game Overlays"),
FSUI_CSTR("Shows icons in the screen when a challenge/primed achievement is active."), "Achievements",
"Overlays", true, enabled);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_PF_HEARTBEAT_ALT, "Enable In-Game Leaderboard Overlays"),
FSUI_CSTR("Shows icons in the screen when leaderboard tracking is active."), "Achievements",
"LBOverlays", true, enabled);
if (enabled)
{
const char* alignment_options[] = {
TRANSLATE_NOOP("FullscreenUI", "Top Left"),
TRANSLATE_NOOP("FullscreenUI", "Top Center"),
TRANSLATE_NOOP("FullscreenUI", "Top Right"),
TRANSLATE_NOOP("FullscreenUI", "Center Left"),
TRANSLATE_NOOP("FullscreenUI", "Center"),
TRANSLATE_NOOP("FullscreenUI", "Center Right"),
TRANSLATE_NOOP("FullscreenUI", "Bottom Left"),
TRANSLATE_NOOP("FullscreenUI", "Bottom Center"),
TRANSLATE_NOOP("FullscreenUI", "Bottom Right")
};
DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_FA_ALIGN_CENTER, "Overlay Position"),
FSUI_CSTR("Determines where achievement/leaderboard overlays are positioned on the screen."), "Achievements", "OverlayPosition",
8, alignment_options, std::size(alignment_options), true, 0, enabled);
const bool notifications_enabled = GetEffectiveBoolSetting(bsi, "Achievements", "Notifications", true) ||
GetEffectiveBoolSetting(bsi, "Achievements", "LeaderboardNotifications", true);
if (notifications_enabled)
{
DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_FA_BELL, "Notification Position"),
FSUI_CSTR("Determines where achievement/leaderboard notification popups are positioned on the screen."), "Achievements", "NotificationPosition",
2, alignment_options, std::size(alignment_options), true, 0, enabled);
}
}
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_LOCK, "Encore Mode"),
FSUI_CSTR("When enabled, each session will behave as if no achievements have been unlocked."), "Achievements", "EncoreMode", false,
enabled);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_EYE, "Spectator Mode"),
FSUI_CSTR("When enabled, PCSX2 will assume all achievements are locked and not send any unlock notifications to the server."),
"Achievements", "SpectatorMode", false, enabled);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_MEDAL, "Test Unofficial Achievements"),
FSUI_CSTR(
"When enabled, PCSX2 will list achievements from unofficial sets. These achievements are not tracked by RetroAchievements."),
"Achievements", "UnofficialTestMode", false, enabled);
// Check for challenge mode just being enabled.
if (check_challenge_state && enabled && bsi->GetBoolValue("Achievements", "ChallengeMode", false) && VMManager::HasValidVM())
{
// don't bother prompting if the game doesn't have achievements
auto lock = Achievements::GetLock();
if (Achievements::HasActiveGame() && Achievements::HasAchievementsOrLeaderboards())
{
ImGuiFullscreen::OpenConfirmMessageDialog(FSUI_STR("Reset System"),
FSUI_STR("Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now?"), [](bool reset) {
if (!VMManager::HasValidVM())
return;
if (reset)
RequestReset();
});
}
}
if (!IsEditingGameSettings(bsi))
{
MenuHeading(FSUI_CSTR("Sound Effects"));
const auto draw_sound_setting = [bsi](const char* title, const char* key, const char* default_filename, const char* selector_title) {
const std::string default_path = Path::Combine(EmuFolders::Resources, default_filename);
const std::optional<SmallString> custom_path = bsi->GetOptionalSmallStringValue("Achievements", key, std::nullopt);
const char* value = custom_path.has_value() ? custom_path->c_str() : default_path.c_str();
if (!MenuButton(title, value))
return;
ImGuiFullscreen::ChoiceDialogOptions options;
options.emplace_back(FSUI_ICONSTR(ICON_FA_FILE, "Select File"), false);
options.emplace_back(FSUI_ICONSTR(ICON_FA_VOLUME_HIGH, "Preview"), false);
options.emplace_back(FSUI_ICONSTR(ICON_FA_ROTATE_RIGHT, "Reset to Default"), false);
OpenChoiceDialog(title, false, std::move(options),
[bsi, key = std::string(key), selector_title = std::string(selector_title), default_path = std::move(default_path)](
s32 index, const std::string&, bool) {
if (index == 0)
{
auto callback = [bsi, key = key](const std::string& path) {
if (!path.empty())
{
bsi->SetStringValue("Achievements", key.c_str(), path.c_str());
SetSettingsChanged(bsi);
}
CloseFileSelector();
};
OpenFileSelector(selector_title.c_str(), false, std::move(callback), GetAudioFileFilters());
}
else if (index == 1)
{
const TinyString preview_path = bsi->GetTinyStringValue("Achievements", key.c_str(), default_path.c_str());
if (!Common::PlaySoundAsync(preview_path.c_str()))
{
ShowToast(std::string(),
fmt::format(FSUI_FSTR("Failed to preview sound:\n{}"),
preview_path.empty() ? FSUI_STR("No file selected.") : preview_path.c_str()));
}
}
else if (index == 2)
{
if (bsi->ContainsValue("Achievements", key.c_str()))
{
bsi->DeleteValue("Achievements", key.c_str());
SetSettingsChanged(bsi);
ShowToast(std::string(), FSUI_STR("Sound reset to default."));
}
else
{
ShowToast(std::string(), FSUI_STR("Sound is already using default."));
}
}
CloseChoiceDialog();
});
};
draw_sound_setting(FSUI_ICONSTR(ICON_FA_MUSIC, "Notification Sound"), "InfoSoundName", "sounds/achievements/message.wav",
FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Select Notification Sound"));
draw_sound_setting(FSUI_ICONSTR(ICON_FA_MUSIC, "Unlock Sound"), "UnlockSoundName", "sounds/achievements/unlock.wav",
FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Select Unlock Sound"));
draw_sound_setting(FSUI_ICONSTR(ICON_FA_MUSIC, "Leaderboard Submit Sound"), "LBSubmitSoundName",
"sounds/achievements/lbsubmit.wav", FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Select Leaderboard Submit Sound"));
MenuHeading(FSUI_CSTR("Account"));
SettingsInterface* secrets_si = Host::Internal::GetSecretsSettingsLayer();
const TinyString username = bsi->GetTinyStringValue("Achievements", "Username", "");
const bool has_token = (secrets_si && secrets_si->ContainsValue("Achievements", "Token"));
if (has_token)
{
ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImGui::GetStyle().Colors[ImGuiCol_Text]);
ActiveButton(SmallString::from_format(
fmt::runtime(FSUI_ICONSTR(ICON_FA_USER, "Username: {}")), username.empty() ? "Unknown" : username.view()),
false, false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
ActiveButton(SmallString::from_format(fmt::runtime(FSUI_ICONSTR(ICON_FA_CLOCK, "Login token generated on {}")),
TimeToPrintableString(static_cast<time_t>(
StringUtil::FromChars<u64>(bsi->GetTinyStringValue("Achievements", "LoginTimestamp", "0")).value_or(0)))),
false, false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
ImGui::PopStyleColor();
if (MenuButton(FSUI_ICONSTR(ICON_FA_KEY, "Logout"), FSUI_CSTR("Logs out of RetroAchievements.")))
{
Host::RunOnCPUThread([]() { Achievements::Logout(); });
}
}
else
{
ActiveButton(FSUI_ICONSTR(ICON_FA_USER, "Not Logged In"), false, false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
if (MenuButton(FSUI_ICONSTR(ICON_FA_KEY, "Login"), FSUI_CSTR("Logs in to RetroAchievements.")))
{
s_achievements_login_reason = Achievements::LoginRequestReason::UserInitiated;
s_achievements_login_show_dismiss = false;
s_achievements_login_open = true;
}
}
MenuHeading(FSUI_CSTR("Current Game"));
if (Achievements::HasActiveGame())
{
const auto lock = Achievements::GetLock();
ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImGui::GetStyle().Colors[ImGuiCol_Text]);
ActiveButton(SmallString::from_format(fmt::runtime(FSUI_ICONSTR(ICON_FA_BOOKMARK, "Game: {0} ({1})")), Achievements::GetGameID(),
Achievements::GetGameTitle()),
false, false, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
const std::string& rich_presence_string = Achievements::GetRichPresenceString();
if (!rich_presence_string.empty())
{
ActiveButton(
SmallString::from_format(ICON_FA_MAP "{}", rich_presence_string), false, false, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
}
else
{
ActiveButton(FSUI_ICONSTR(ICON_FA_MAP, "Rich presence inactive or unsupported."), false, false,
LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
}
ImGui::PopStyleColor();
}
else
{
ActiveButton(FSUI_ICONSTR(ICON_FA_BAN, "Game not loaded or no RetroAchievements available."), false, false,
LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
}
}
EndMenuButtons();
}
void FullscreenUI::ReportStateLoadError(const std::string& message, std::optional<s32> slot, bool backup)
{
MTGS::RunOnGSThread([message, slot, backup]() {
+571 -3
View File
@@ -26,6 +26,7 @@
#endif
#include "common/Console.h"
#include "common/Error.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include "common/SettingsInterface.h"
@@ -2488,6 +2489,10 @@ void FullscreenUI::DrawBIOSSettingsPage()
MenuHeading(FSUI_CSTR("Fast Boot Options"));
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_FORWARD_FAST, "Fast Boot"), FSUI_CSTR("Skips the intro screen, and bypasses region checks."),
"EmuCore", "EnableFastBoot", true);
const bool fast_boot_enabled = GetEffectiveBoolSetting(bsi, "EmuCore", "EnableFastBoot", true);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_FORWARD, "Fast Forward Boot"),
FSUI_CSTR("Removes emulation speed throttle until the game starts to reduce startup time."),
"EmuCore", "EnableFastBootFastForward", false, fast_boot_enabled);
EndMenuButtons();
}
@@ -2974,6 +2979,9 @@ void FullscreenUI::DrawGraphicsSettingsPage(SettingsInterface* bsi, bool show_ad
FSUI_CSTR("Selects the algorithm used to convert the PS2's interlaced output to progressive for display."), "EmuCore/GS",
"deinterlace_mode", static_cast<int>(GSInterlaceMode::Automatic), s_deinterlacing_options, std::size(s_deinterlacing_options),
true);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_TV, "Disable Interlace Offset"),
FSUI_CSTR("Disables interlacing offset which may reduce blurring in some situations."), "EmuCore/GS",
"disable_interlace_offset", false);
DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_FA_ARROWS_UP_DOWN_LEFT_RIGHT, "Screenshot Size"), FSUI_CSTR("Determines the resolution at which screenshots will be saved."),
"EmuCore/GS", "ScreenshotSize", static_cast<int>(GSScreenshotSize::WindowResolution), s_screenshot_sizes,
std::size(s_screenshot_sizes), true);
@@ -3169,6 +3177,9 @@ void FullscreenUI::DrawGraphicsSettingsPage(SettingsInterface* bsi, bool show_ad
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_PALETTE, "GPU Palette Conversion"),
FSUI_CSTR("When enabled GPU converts colormap-textures, otherwise the CPU will. It is a trade-off between GPU and CPU."),
"EmuCore/GS", "paltex", false, manual_hw_fixes);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_LAYER_GROUP, "Draw Buffering"),
FSUI_CSTR("Attempts to reduce draw calls in games which do heavy context switching for blending purposes."),
"EmuCore/GS", "UserHacks_DrawBuffering", false, manual_hw_fixes);
MenuHeading(FSUI_CSTR("Upscaling Fixes"));
DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_FA_ARROWS_UP_DOWN_LEFT_RIGHT, "Half Pixel Offset"), FSUI_CSTR("Adjusts vertices relative to upscaling."), "EmuCore/GS",
@@ -3487,10 +3498,10 @@ void FullscreenUI::DrawAudioSettingsPage()
DrawIntRangeSetting(bsi, FSUI_ICONSTR(ICON_FA_VOLUME_HIGH, "Standard Volume"),
FSUI_CSTR("Controls the volume of the audio played on the host at normal speed."), "SPU2/Output", "StandardVolume", 100,
0, 100, "%d%%");
0, 200, "%d%%");
DrawIntRangeSetting(bsi, FSUI_ICONSTR(ICON_FA_FORWARD_FAST, "Fast Forward Volume"),
FSUI_CSTR("Controls the volume of the audio played on the host when fast forwarding."), "SPU2/Output",
"FastForwardVolume", 100, 0, 100, "%d%%");
"FastForwardVolume", 100, 0, 200, "%d%%");
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_VOLUME_XMARK, "Mute All Sound"),
FSUI_CSTR("Prevents the emulator from producing any audible sound."), "SPU2/Output", "OutputMuted",
false);
@@ -3544,6 +3555,31 @@ void FullscreenUI::DrawMemoryCardSettingsPage()
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_MAGNIFYING_GLASS, "Folder Memory Card Filter"),
FSUI_CSTR("Simulates a larger memory card by filtering saves only to the current game."), "EmuCore", "McdFolderAutoManage", true);
if (MenuButton(FSUI_ICONSTR(ICON_FA_ARROWS_ROTATE, "Swap Memory Cards"), FSUI_CSTR("Swaps the selected memory cards in Slot 1 and Slot 2.")))
{
SettingsInterface* ebsi = GetEditingSettingsInterface();
std::string card1;
if (!ebsi->GetStringValue("MemoryCards", "Slot1_Filename", &card1))
card1 = Host::GetBaseStringSettingValue("MemoryCards", "Slot1_Filename", FileMcd_GetDefaultName(0).c_str());
std::string card2;
if (!ebsi->GetStringValue("MemoryCards", "Slot2_Filename", &card2))
card2 = Host::GetBaseStringSettingValue("MemoryCards", "Slot2_Filename", FileMcd_GetDefaultName(1).c_str());
if (card1.empty() || card2.empty())
{
ShowToast(std::string(), FSUI_STR("Both slots must have a card selected to swap."));
}
else
{
ebsi->SetStringValue("MemoryCards", "Slot1_Filename", card2.c_str());
ebsi->SetStringValue("MemoryCards", "Slot2_Filename", card1.c_str());
SetSettingsChanged(ebsi);
ShowToast(std::string(), FSUI_STR("Swapped Slot 1 and Slot 2 memory cards."));
}
}
for (u32 port = 0; port < NUM_MEMORY_CARD_PORTS; port++)
{
SmallString str;
@@ -4296,6 +4332,512 @@ void FullscreenUI::DoCreateMemoryCard(std::string name, MemoryCardType type, Mem
ShowToast(std::string(), fmt::format(FSUI_FSTR("Memory Card '{}' created."), name));
}
void FullscreenUI::DrawAchievementsLoginWindow()
{
if (s_achievements_login_open && !ImGui::IsPopupOpen("RetroAchievements"))
ImGui::OpenPopup("RetroAchievements");
const float dialog_width = std::clamp(LayoutScale(420.0f), 300.0f, ImGui::GetIO().DisplaySize.x);
ImGui::SetNextWindowSizeConstraints(ImVec2(dialog_width, 0.0f), ImVec2(dialog_width, ImGui::GetIO().DisplaySize.y));
ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(ImGuiFullscreen::LAYOUT_WINDOW_ROUNDING));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(24.0f, 24.0f));
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.13f, 0.13f, 0.13f, 0.95f));
if (ImGui::BeginPopupModal("RetroAchievements", &s_achievements_login_open, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize))
{
const float content_width = ImGui::GetContentRegionAvail().x;
ImGui::PushFont(g_large_font.first, g_large_font.second);
const float icon_height = LayoutScale(24.0f);
const float icon_width = icon_height * (500.0f / 275.0f);
GSTexture* ra_icon = GetCachedSvgTextureAsync("icons/ra-icon.svg", ImVec2(icon_width, icon_height));
const float title_width = ImGui::CalcTextSize("RetroAchievements").x;
const float header_width = (ra_icon ? icon_width + LayoutScale(10.0f) : 0.0f) + title_width;
const float header_start = (content_width - header_width) * 0.5f;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + header_start);
if (ra_icon)
{
ImGui::Image(reinterpret_cast<ImTextureID>(ra_icon->GetNativeHandle()),
ImVec2(icon_width, icon_height));
ImGui::SameLine();
}
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(1.0f));
ImGui::TextUnformatted(FSUI_CSTR("RetroAchievements"));
ImGui::PopFont();
ImGui::Spacing();
ImGui::Spacing();
ImGui::PushTextWrapPos(content_width);
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.8f, 0.8f, 0.8f, 1.0f));
ImGui::TextWrapped("%s", FSUI_CSTR("Please enter your user name and password for retroachievements.org below.\n\nYour password will not be saved in PCSX2, an access token will be generated and used instead."));
ImGui::PopStyleColor();
ImGui::PopTextWrapPos();
ImGui::Spacing();
ImGui::Spacing();
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, LayoutScale(ImGuiFullscreen::LAYOUT_FRAME_ROUNDING));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, LayoutScale(12.0f, 10.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, LayoutScale(1.0f));
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.2f, 0.2f, 0.2f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.25f, 0.25f, 0.25f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.4f, 0.4f, 0.4f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
if (s_achievements_login_logging_in || s_achievements_login_show_dismiss)
ImGui::BeginDisabled();
ImGui::SetNextItemWidth(content_width);
ImGui::InputTextWithHint("##username", FSUI_CSTR("Username"), s_achievements_login_username, sizeof(s_achievements_login_username));
ImGui::Spacing();
ImGui::SetNextItemWidth(content_width);
ImGui::InputTextWithHint("##password", FSUI_CSTR("Password"), s_achievements_login_password, sizeof(s_achievements_login_password), ImGuiInputTextFlags_Password);
ImGui::PopStyleColor(5);
ImGui::PopStyleVar(3);
if (s_achievements_login_logging_in || s_achievements_login_show_dismiss)
ImGui::EndDisabled();
ImGui::Spacing();
ImGui::Spacing();
if (s_achievements_login_logging_in)
{
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f));
const float status_width = ImGui::CalcTextSize("Logging in...").x;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (content_width - status_width) * 0.5f);
ImGui::TextUnformatted(FSUI_CSTR("Logging in..."));
ImGui::PopStyleColor();
ImGui::Spacing();
}
else if (s_achievements_login_show_dismiss)
{
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.35f, 0.9f, 0.35f, 1.0f));
const TinyString username = Host::GetBaseTinyStringSettingValue("Achievements", "Username", "");
const SmallString success_text = SmallString::from_format(
FSUI_FSTR("Successfully logged in as {}."), username.empty() ? "Unknown" : username.view());
const float status_width = ImGui::CalcTextSize(success_text.c_str()).x;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (content_width - status_width) * 0.5f);
ImGui::TextUnformatted(success_text.c_str());
ImGui::PopStyleColor();
ImGui::Spacing();
}
const float button_height = std::max(LayoutScale(36.0f), 28.0f);
const float button_spacing = LayoutScale(12.0f);
const float button_width = (content_width - button_spacing) * 0.5f;
auto CloseLoginPopup = []() {
ImGui::CloseCurrentPopup();
s_achievements_login_open = false;
s_achievements_login_logging_in = false;
s_achievements_login_show_dismiss = false;
s_achievements_login_username[0] = '\0';
s_achievements_login_password[0] = '\0';
};
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, LayoutScale(ImGuiFullscreen::LAYOUT_FRAME_ROUNDING));
const bool can_login = !s_achievements_login_show_dismiss && !s_achievements_login_logging_in &&
strlen(s_achievements_login_username) > 0 &&
strlen(s_achievements_login_password) > 0;
if (s_achievements_login_show_dismiss)
{
// keep dialog open and let user explicitly dismiss.
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.5f, 0.9f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.6f, 1.0f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.1f, 0.4f, 0.8f, 1.0f));
if (ImGui::Button(FSUI_CSTR("Dismiss"), ImVec2(button_width, button_height)) && !s_achievements_login_logging_in)
CloseLoginPopup();
ImGui::PopStyleColor(3);
ImGui::SameLine(0, button_spacing);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
ImGui::BeginDisabled();
ImGui::Button(FSUI_CSTR("Login"), ImVec2(button_width, button_height));
ImGui::EndDisabled();
ImGui::PopStyleColor(3);
ImGui::PopStyleVar();
ImGui::EndPopup();
ImGui::PopStyleColor();
ImGui::PopStyleVar(2);
return;
}
if (can_login)
{
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.5f, 0.9f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.6f, 1.0f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.1f, 0.4f, 0.8f, 1.0f));
}
else
{
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
}
if (ImGui::Button(FSUI_CSTR("Login"), ImVec2(button_width, button_height)) && can_login)
{
s_achievements_login_logging_in = true;
s_achievements_login_show_dismiss = false;
Host::RunOnCPUThread([username = std::string(s_achievements_login_username),
password = std::string(s_achievements_login_password)]() {
Error error;
const bool result = Achievements::Login(username.c_str(), password.c_str(), &error);
s_achievements_login_logging_in = false;
if (!result)
{
ShowToast(std::string(), fmt::format(FSUI_FSTR("Login failed.\nError: {}\n\nPlease check your username and password, and try again."),
error.GetDescription()));
return;
}
s_achievements_login_password[0] = '\0';
if (s_achievements_login_reason == Achievements::LoginRequestReason::UserInitiated)
{
if (!Host::GetBaseBoolSettingValue("Achievements", "Enabled", false))
{
OpenConfirmMessageDialog(FSUI_STR("Enable Achievements"),
FSUI_STR("Achievement tracking is not currently enabled. Your login will have no effect until "
"after tracking is enabled.\n\nDo you want to enable tracking now?"),
[](bool result) {
if (result)
{
Host::SetBaseBoolSettingValue("Achievements", "Enabled", true);
Host::CommitBaseSettingChanges();
VMManager::ApplySettings();
}
});
}
if (!Host::GetBaseBoolSettingValue("Achievements", "ChallengeMode", false))
{
OpenConfirmMessageDialog(FSUI_STR("Enable Hardcore Mode"),
FSUI_STR("Hardcore mode is not currently enabled. Enabling hardcore mode allows you to set times, scores, and "
"participate in game-specific leaderboards.\n\nHowever, hardcore mode also prevents the usage of save "
"states, cheats and slowdown functionality.\n\nDo you want to enable hardcore mode?"),
[](bool result) {
if (result)
{
Host::SetBaseBoolSettingValue("Achievements", "ChallengeMode", true);
Host::CommitBaseSettingChanges();
VMManager::ApplySettings();
bool has_active_game;
{
auto lock = Achievements::GetLock();
has_active_game = Achievements::HasActiveGame();
}
if (has_active_game)
{
OpenConfirmMessageDialog(FSUI_STR("Reset System"),
FSUI_STR("Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now?"),
[](bool reset) {
if (reset && VMManager::HasValidVM())
RequestReset();
});
}
}
});
}
}
s_achievements_login_show_dismiss = true;
});
}
ImGui::PopStyleColor(3);
ImGui::SameLine(0, button_spacing);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.4f, 0.4f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.5f, 0.5f, 0.5f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
if (ImGui::Button(FSUI_CSTR("Cancel"), ImVec2(button_width, button_height)) && !s_achievements_login_logging_in)
{
if (s_achievements_login_reason == Achievements::LoginRequestReason::TokenInvalid)
{
if (VMManager::HasValidVM() && !Achievements::HasActiveGame())
Achievements::DisableHardcoreMode();
}
CloseLoginPopup();
}
ImGui::PopStyleColor(3);
ImGui::PopStyleVar();
ImGui::EndPopup();
}
ImGui::PopStyleColor();
ImGui::PopStyleVar(2);
}
void FullscreenUI::DrawAchievementsSettingsPage(std::unique_lock<std::mutex>& settings_lock)
{
#ifdef ENABLE_RAINTEGRATION
if (Achievements::IsUsingRAIntegration())
{
BeginMenuButtons();
ActiveButton(FSUI_ICONSTR(ICON_FA_BAN, "RAIntegration is being used instead of the built-in achievements implementation."), false,
false, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
EndMenuButtons();
return;
}
#endif
SettingsInterface* bsi = GetEditingSettingsInterface();
bool check_challenge_state = false;
BeginMenuButtons();
MenuHeading(FSUI_CSTR("Settings"));
check_challenge_state = DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_TROPHY, "Enable Achievements"),
FSUI_CSTR("When enabled and logged in, PCSX2 will scan for achievements on startup."), "Achievements", "Enabled", false);
const bool enabled = bsi->GetBoolValue("Achievements", "Enabled", false);
check_challenge_state |= DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_PF_DUMBELL, "Hardcore Mode"),
FSUI_CSTR(
"\"Challenge\" mode for achievements, including leaderboard tracking. Disables save state, cheats, and slowdown functions."),
"Achievements", "ChallengeMode", false, enabled);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_BELL, "Achievement Notifications"),
FSUI_CSTR("Displays popup messages on events such as achievement unlocks and leaderboard submissions."), "Achievements",
"Notifications", true, enabled);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_LIST_OL, "Leaderboard Notifications"),
FSUI_CSTR("Displays popup messages when starting, submitting, or failing a leaderboard challenge."), "Achievements",
"LeaderboardNotifications", true, enabled);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_HEADPHONES, "Sound Effects"),
FSUI_CSTR("Plays sound effects for events such as achievement unlocks and leaderboard submissions."), "Achievements",
"SoundEffects", true, enabled);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_PF_HEARTBEAT_ALT, "Enable In-Game Overlays"),
FSUI_CSTR("Shows icons in the screen when a challenge/primed achievement is active."), "Achievements",
"Overlays", true, enabled);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_PF_HEARTBEAT_ALT, "Enable In-Game Leaderboard Overlays"),
FSUI_CSTR("Shows icons in the screen when leaderboard tracking is active."), "Achievements",
"LBOverlays", true, enabled);
if (enabled)
{
const char* alignment_options[] = {
TRANSLATE_NOOP("FullscreenUI", "Top Left"),
TRANSLATE_NOOP("FullscreenUI", "Top Center"),
TRANSLATE_NOOP("FullscreenUI", "Top Right"),
TRANSLATE_NOOP("FullscreenUI", "Center Left"),
TRANSLATE_NOOP("FullscreenUI", "Center"),
TRANSLATE_NOOP("FullscreenUI", "Center Right"),
TRANSLATE_NOOP("FullscreenUI", "Bottom Left"),
TRANSLATE_NOOP("FullscreenUI", "Bottom Center"),
TRANSLATE_NOOP("FullscreenUI", "Bottom Right")};
DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_FA_ALIGN_CENTER, "Overlay Position"),
FSUI_CSTR("Determines where achievement/leaderboard overlays are positioned on the screen."), "Achievements", "OverlayPosition",
8, alignment_options, std::size(alignment_options), true, 0, enabled);
const bool notifications_enabled = GetEffectiveBoolSetting(bsi, "Achievements", "Notifications", true) ||
GetEffectiveBoolSetting(bsi, "Achievements", "LeaderboardNotifications", true);
if (notifications_enabled)
{
DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_FA_BELL, "Notification Position"),
FSUI_CSTR("Determines where achievement/leaderboard notification popups are positioned on the screen."), "Achievements", "NotificationPosition",
2, alignment_options, std::size(alignment_options), true, 0, enabled);
const bool achievement_notifications_enabled = enabled && GetEffectiveBoolSetting(bsi, "Achievements", "Notifications", true);
DrawIntRangeSetting(bsi, FSUI_ICONSTR(ICON_FA_CLOCK, "Unlock Notification Duration"),
FSUI_CSTR("Determines the display duration for achievement unlock popups."),
"Achievements", "NotificationsDuration", 5, 3, 30, "%d seconds", achievement_notifications_enabled);
const bool leaderboard_notifications_enabled = enabled && GetEffectiveBoolSetting(bsi, "Achievements", "LeaderboardNotifications", true);
DrawIntRangeSetting(bsi, FSUI_ICONSTR(ICON_FA_CLOCK, "Leaderboard Notification Duration"),
FSUI_CSTR("Determines the display duration for leaderboard popups."),
"Achievements", "LeaderboardsDuration", 10, 3, 30, "%d seconds", leaderboard_notifications_enabled);
}
}
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_LOCK, "Encore Mode"),
FSUI_CSTR("When enabled, each session will behave as if no achievements have been unlocked."), "Achievements", "EncoreMode", false,
enabled);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_EYE, "Spectator Mode"),
FSUI_CSTR("When enabled, PCSX2 will assume all achievements are locked and not send any unlock notifications to the server."),
"Achievements", "SpectatorMode", false, enabled);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_MEDAL, "Test Unofficial Achievements"),
FSUI_CSTR(
"When enabled, PCSX2 will list achievements from unofficial sets. These achievements are not tracked by RetroAchievements."),
"Achievements", "UnofficialTestMode", false, enabled);
// Check for challenge mode just being enabled.
if (check_challenge_state && enabled && bsi->GetBoolValue("Achievements", "ChallengeMode", false) && VMManager::HasValidVM())
{
// don't bother prompting if the game doesn't have achievements
auto lock = Achievements::GetLock();
if (Achievements::HasActiveGame() && Achievements::HasAchievementsOrLeaderboards())
{
ImGuiFullscreen::OpenConfirmMessageDialog(FSUI_STR("Reset System"),
FSUI_STR("Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now?"), [](bool reset) {
if (!VMManager::HasValidVM())
return;
if (reset)
RequestReset();
});
}
}
if (!IsEditingGameSettings(bsi))
{
MenuHeading(FSUI_CSTR("Sound Effects"));
const auto draw_sound_setting = [bsi](const char* title, const char* key, const char* default_filename, const char* selector_title) {
const std::string default_path = Path::Combine(EmuFolders::Resources, default_filename);
const std::optional<SmallString> custom_path = bsi->GetOptionalSmallStringValue("Achievements", key, std::nullopt);
const char* value = custom_path.has_value() ? custom_path->c_str() : default_path.c_str();
if (!MenuButton(title, value))
return;
ImGuiFullscreen::ChoiceDialogOptions options;
options.emplace_back(FSUI_ICONSTR(ICON_FA_FILE, "Select File"), false);
options.emplace_back(FSUI_ICONSTR(ICON_FA_VOLUME_HIGH, "Preview"), false);
options.emplace_back(FSUI_ICONSTR(ICON_FA_ROTATE_RIGHT, "Reset to Default"), false);
OpenChoiceDialog(title, false, std::move(options),
[bsi, key = std::string(key), selector_title = std::string(selector_title), default_path = std::move(default_path)](
s32 index, const std::string&, bool) {
if (index == 0)
{
auto callback = [bsi, key = key](const std::string& path) {
if (!path.empty())
{
bsi->SetStringValue("Achievements", key.c_str(), path.c_str());
SetSettingsChanged(bsi);
}
CloseFileSelector();
};
OpenFileSelector(selector_title.c_str(), false, std::move(callback), GetAudioFileFilters());
}
else if (index == 1)
{
const TinyString preview_path = bsi->GetTinyStringValue("Achievements", key.c_str(), default_path.c_str());
if (!Common::PlaySoundAsync(preview_path.c_str()))
{
ShowToast(std::string(),
fmt::format(FSUI_FSTR("Failed to preview sound:\n{}"),
preview_path.empty() ? FSUI_STR("No file selected.") : preview_path.c_str()));
}
}
else if (index == 2)
{
if (bsi->ContainsValue("Achievements", key.c_str()))
{
bsi->DeleteValue("Achievements", key.c_str());
SetSettingsChanged(bsi);
ShowToast(std::string(), FSUI_STR("Sound reset to default."));
}
else
{
ShowToast(std::string(), FSUI_STR("Sound is already using default."));
}
}
CloseChoiceDialog();
});
};
draw_sound_setting(FSUI_ICONSTR(ICON_FA_MUSIC, "Notification Sound"), "InfoSoundName", "sounds/achievements/message.wav",
FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Select Notification Sound"));
draw_sound_setting(FSUI_ICONSTR(ICON_FA_MUSIC, "Unlock Sound"), "UnlockSoundName", "sounds/achievements/unlock.wav",
FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Select Unlock Sound"));
draw_sound_setting(FSUI_ICONSTR(ICON_FA_MUSIC, "Leaderboard Submit Sound"), "LBSubmitSoundName",
"sounds/achievements/lbsubmit.wav", FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Select Leaderboard Submit Sound"));
MenuHeading(FSUI_CSTR("Account"));
SettingsInterface* secrets_si = Host::Internal::GetSecretsSettingsLayer();
const TinyString username = bsi->GetTinyStringValue("Achievements", "Username", "");
const bool has_token = (secrets_si && secrets_si->ContainsValue("Achievements", "Token"));
if (has_token)
{
ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImGui::GetStyle().Colors[ImGuiCol_Text]);
ActiveButton(SmallString::from_format(
fmt::runtime(FSUI_ICONSTR(ICON_FA_USER, "Username: {}")), username.empty() ? "Unknown" : username.view()),
false, false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
ActiveButton(SmallString::from_format(fmt::runtime(FSUI_ICONSTR(ICON_FA_CLOCK, "Login token generated on {}")),
TimeToPrintableString(static_cast<time_t>(
StringUtil::FromChars<u64>(bsi->GetTinyStringValue("Achievements", "LoginTimestamp", "0")).value_or(0)))),
false, false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
ImGui::PopStyleColor();
if (MenuButton(FSUI_ICONSTR(ICON_FA_KEY, "Logout"), FSUI_CSTR("Logs out of RetroAchievements.")))
{
Host::RunOnCPUThread([]() { Achievements::Logout(); });
}
}
else
{
ActiveButton(FSUI_ICONSTR(ICON_FA_USER, "Not Logged In"), false, false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
if (MenuButton(FSUI_ICONSTR(ICON_FA_KEY, "Login"), FSUI_CSTR("Logs in to RetroAchievements.")))
{
s_achievements_login_reason = Achievements::LoginRequestReason::UserInitiated;
s_achievements_login_show_dismiss = false;
s_achievements_login_open = true;
}
}
MenuHeading(FSUI_CSTR("Current Game"));
if (Achievements::HasActiveGame())
{
const auto lock = Achievements::GetLock();
ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImGui::GetStyle().Colors[ImGuiCol_Text]);
ActiveButton(SmallString::from_format(fmt::runtime(FSUI_ICONSTR(ICON_FA_BOOKMARK, "Game: {0} ({1})")), Achievements::GetGameID(),
Achievements::GetGameTitle()),
false, false, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
const std::string& rich_presence_string = Achievements::GetRichPresenceString();
if (!rich_presence_string.empty())
{
ActiveButton(
SmallString::from_format(ICON_FA_MAP "{}", rich_presence_string), false, false, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
}
else
{
ActiveButton(FSUI_ICONSTR(ICON_FA_MAP, "Rich presence inactive or unsupported."), false, false,
LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
}
ImGui::PopStyleColor();
}
else
{
ActiveButton(FSUI_ICONSTR(ICON_FA_BAN, "Game not loaded or no RetroAchievements available."), false, false,
LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
}
}
EndMenuButtons();
}
void FullscreenUI::ResetControllerSettings()
{
OpenConfirmMessageDialog(FSUI_ICONSTR(u8"🔥", "Reset Controller Settings"),
@@ -4862,6 +5404,12 @@ void FullscreenUI::DrawFoldersSettingsPage()
DrawFolderSetting(bsi, FSUI_ICONSTR(ICON_FA_SHIRT, "Texture Replacements Directory"), "Folders", "Textures", EmuFolders::Textures);
DrawFolderSetting(bsi, FSUI_ICONSTR(ICON_FA_VIDEO, "Video Dumping Directory"), "Folders", "Videos", EmuFolders::Videos);
MenuHeading(FSUI_CSTR("Organization"));
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Save Snapshots in Game-Specific Folders"),
FSUI_CSTR("Saves snapshots to per-game subfolders instead of a shared folder."), "EmuCore/GS", "OrganizeScreenshotsByGame", false);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Save Video Recordings in Game-Specific Folders"),
FSUI_CSTR("Saves video recordings to per-game subfolders instead of a shared folder."), "EmuCore/GS", "OrganizeVideoCaptureByGame", false);
EndMenuButtons();
}
@@ -4874,6 +5422,13 @@ void FullscreenUI::DrawAdvancedSettingsPage()
FSUI_NSTR("Chop/Zero (Default)"),
};
static constexpr const char* ee_div_rounding_mode_settings[] = {
FSUI_NSTR("Nearest (Default)"),
FSUI_NSTR("Negative"),
FSUI_NSTR("Positive"),
FSUI_NSTR("Chop/Zero"),
};
SettingsInterface* bsi = GetEditingSettingsInterface();
const bool show_advanced_settings = ShouldShowAdvancedSettings(bsi);
@@ -4933,7 +5488,7 @@ void FullscreenUI::DrawAdvancedSettingsPage()
DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_FA_DIVIDE, "Division Rounding Mode"),
FSUI_CSTR("Determines how the results of floating-point division is rounded. Some games need specific settings."),
"EmuCore/CPU", "FPUDiv.Roundmode", static_cast<int>(FPRoundMode::Nearest),
ee_rounding_mode_settings, std::size(ee_rounding_mode_settings), true);
ee_div_rounding_mode_settings, std::size(ee_div_rounding_mode_settings), true);
DrawClampingModeSetting(bsi, FSUI_ICONSTR(ICON_FA_ARROW_TURN_DOWN, "Clamping Mode"),
FSUI_CSTR("Determines how out-of-range floating point numbers are handled. Some games need specific settings."), -1);
@@ -4949,6 +5504,12 @@ void FullscreenUI::DrawAdvancedSettingsPage()
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_MEMORY, "Enable Fast Memory Access"),
FSUI_CSTR("Uses backpatching to avoid register flushing on every memory access."), "EmuCore/CPU/Recompiler", "EnableFastmem",
true);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_PAUSE, "Pause On TLB Miss"),
FSUI_CSTR("Pauses the virtual machine when a TLB miss occurs, instead of ignoring it and continuing."),
"EmuCore/CPU/Recompiler", "PauseOnTLBMiss", false);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_MEMORY, "Enable Extended RAM (Dev Console)"),
FSUI_CSTR("Exposes additional memory to the virtual machine, expanding the EE and IOP memory to 128MB and 8MB respectively."),
"EmuCore/CPU", "ExtraMemory", false);
MenuHeading(FSUI_CSTR("Vector Units"));
DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_FA_ARROW_TREND_DOWN, "VU0 Rounding Mode"),
@@ -4991,6 +5552,13 @@ void FullscreenUI::DrawAdvancedSettingsPage()
"UseDebugDevice", false);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_BUG, "Use Debug Blend"), FSUI_CSTR("Forces SW blending and disables several optimizations."), "EmuCore/GS", "UseDebugBlend", false);
MenuHeading(FSUI_CSTR("PINE Settings"));
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_NETWORK_WIRED, "Enable PINE"),
FSUI_CSTR("Enables the PINE Inter-Process Communication system, allowing external programs to interact with the emulator."), "EmuCore", "EnablePINE", false);
const bool pine_enabled = GetEffectiveBoolSetting(bsi, "EmuCore", "EnablePINE", false);
DrawIntSpinBoxSetting(bsi, FSUI_ICONSTR(ICON_FA_NETWORK_WIRED, "PINE Slot"),
FSUI_CSTR("The network port slot used for PINE IPC connections."), "EmuCore", "PINESlot", 28011, 1024, 65535, 1, "%d", pine_enabled);
}
EndMenuButtons();
+137 -23
View File
@@ -100,6 +100,22 @@ static std::atomic_bool s_imgui_wants_text{false};
static bool s_gamepad_swap_noth_west = false;
struct ControllerNavState
{
bool dpad_h_held = false;
bool dpad_v_held = false;
struct AxisState
{
float x = 0.0f, y = 0.0f;
bool x_neg_active = false, x_pos_active = false;
bool y_neg_active = false, y_pos_active = false;
};
AxisState left_stick;
AxisState right_stick;
};
static std::unordered_map<u32, ControllerNavState> s_controller_nav_states;
// mapping of host key -> imgui key
static std::unordered_map<u32, ImGuiKey> s_imgui_key_map;
@@ -524,7 +540,15 @@ static u32 GetFontIndex(const ImGuiManager::FontInfo& font)
{
if (!font.face_name)
return 0; // No face name selected
#define RET_IF_ERR(x) do { if ((x) != FT_Err_Ok) [[unlikely]] { assert(0); return 0; } } while (0)
#define RET_IF_ERR(x) \
do \
{ \
if ((x) != FT_Err_Ok) [[unlikely]] \
{ \
assert(0); \
return 0; \
} \
} while (0)
if (!s_ft_lib)
RET_IF_ERR(FT_Init_FreeType(&s_ft_lib));
u32 face_idx = 0;
@@ -897,7 +921,7 @@ void ImGuiManager::DrawOSDMessages(Common::Timer::Value current_time)
const float padding = std::ceil(8.0f * scale);
const float rounding = std::ceil(5.0f * scale);
const float max_width = s_window_width - (margin + padding) * 2.0f;
float position_y = margin;
switch (GSConfig.OsdMessagesPos)
{
@@ -906,20 +930,20 @@ void ImGuiManager::DrawOSDMessages(Common::Timer::Value current_time)
case OsdOverlayPos::TopRight:
position_y = margin;
break;
case OsdOverlayPos::CenterLeft:
case OsdOverlayPos::Center:
case OsdOverlayPos::CenterRight:
position_y = s_window_height * 0.5f;
break;
case OsdOverlayPos::BottomLeft:
case OsdOverlayPos::BottomCenter:
case OsdOverlayPos::BottomRight:
// For bottom positions, start from the bottom and let messages stack upward
position_y = s_window_height - margin;
break;
case OsdOverlayPos::None:
default:
position_y = margin;
@@ -991,16 +1015,16 @@ void ImGuiManager::DrawOSDMessages(Common::Timer::Value current_time)
const ImVec2 text_size(
font->CalcTextSizeA(font_size, max_width, max_width, msg.text.c_str(), msg.text.c_str() + msg.text.length()));
const ImVec2 size(text_size.x + padding * 2.0f, text_size.y + padding * 2.0f);
// For bottom positions, adjust actual_y to try to account for message height
float final_y = actual_y;
if (GSConfig.OsdMessagesPos == OsdOverlayPos::BottomLeft ||
GSConfig.OsdMessagesPos == OsdOverlayPos::BottomCenter ||
GSConfig.OsdMessagesPos == OsdOverlayPos::BottomRight)
if (GSConfig.OsdMessagesPos == OsdOverlayPos::BottomLeft ||
GSConfig.OsdMessagesPos == OsdOverlayPos::BottomCenter ||
GSConfig.OsdMessagesPos == OsdOverlayPos::BottomRight)
{
final_y = actual_y - size.y;
}
const ImVec2 base_pos = CalculateOSDPosition(GSConfig.OsdMessagesPos, margin, size, s_window_width, s_window_height);
const ImVec2 pos(base_pos.x, final_y);
const ImVec4 text_rect(pos.x + padding, pos.y + padding, pos.x + size.x - padding, pos.y + size.y - padding);
@@ -1010,11 +1034,11 @@ void ImGuiManager::DrawOSDMessages(Common::Timer::Value current_time)
dl->AddRect(pos, ImVec2(pos.x + size.x, pos.y + size.y), IM_COL32(0x48, 0x48, 0x48, opacity), rounding);
dl->AddText(font, font_size, ImVec2(text_rect.x, text_rect.y), IM_COL32(0xff, 0xff, 0xff, opacity), msg.text.c_str(),
msg.text.c_str() + msg.text.length(), max_width, &text_rect);
// Stack direction depends on the position upward for bottom positions, downward for others
if (GSConfig.OsdMessagesPos == OsdOverlayPos::BottomLeft ||
GSConfig.OsdMessagesPos == OsdOverlayPos::BottomCenter ||
GSConfig.OsdMessagesPos == OsdOverlayPos::BottomRight)
if (GSConfig.OsdMessagesPos == OsdOverlayPos::BottomLeft ||
GSConfig.OsdMessagesPos == OsdOverlayPos::BottomCenter ||
GSConfig.OsdMessagesPos == OsdOverlayPos::BottomRight)
{
position_y -= (size.y + spacing); // Stack upward for bottom positions
}
@@ -1180,7 +1204,7 @@ bool ImGuiManager::ProcessHostKeyEvent(InputBindingKey key, float value)
return s_imgui_wants_keyboard.load(std::memory_order_acquire);
}
bool ImGuiManager::ProcessGenericInputEvent(GenericInputBinding key, InputLayout layout, float value)
bool ImGuiManager::ProcessGenericInputEvent(GenericInputBinding key, InputLayout layout, float value, u32 controller_id)
{
static constexpr ImGuiKey key_map[] = {
ImGuiKey_None, // Unknown,
@@ -1188,15 +1212,15 @@ bool ImGuiManager::ProcessGenericInputEvent(GenericInputBinding key, InputLayout
ImGuiKey_GamepadDpadRight, // DPadRight
ImGuiKey_GamepadDpadLeft, // DPadLeft
ImGuiKey_GamepadDpadDown, // DPadDown
ImGuiKey_None, // LeftStickUp
ImGuiKey_None, // LeftStickRight
ImGuiKey_None, // LeftStickDown
ImGuiKey_None, // LeftStickLeft
ImGuiKey_GamepadDpadUp, // LeftStickUp
ImGuiKey_GamepadDpadRight, // LeftStickRight
ImGuiKey_GamepadDpadDown, // LeftStickDown
ImGuiKey_GamepadDpadLeft, // LeftStickLeft
ImGuiKey_GamepadL3, // L3
ImGuiKey_None, // RightStickUp
ImGuiKey_None, // RightStickRight
ImGuiKey_None, // RightStickDown
ImGuiKey_None, // RightStickLeft
ImGuiKey_GamepadDpadUp, // RightStickUp
ImGuiKey_GamepadDpadRight, // RightStickRight
ImGuiKey_GamepadDpadDown, // RightStickDown
ImGuiKey_GamepadDpadLeft, // RightStickLeft
ImGuiKey_GamepadR3, // R3
ImGuiKey_GamepadFaceUp, // Triangle
ImGuiKey_GamepadFaceRight, // Circle
@@ -1217,6 +1241,21 @@ bool ImGuiManager::ProcessGenericInputEvent(GenericInputBinding key, InputLayout
if (static_cast<u32>(key) >= std::size(key_map) || key_map[static_cast<u32>(key)] == ImGuiKey_None)
return false;
// Ignore diagonal D-pad input — neither direction fires when both axes are held simultaneously.
const bool is_dpad_h = (key == GenericInputBinding::DPadLeft || key == GenericInputBinding::DPadRight);
const bool is_dpad_v = (key == GenericInputBinding::DPadUp || key == GenericInputBinding::DPadDown);
if (is_dpad_h || is_dpad_v)
{
ControllerNavState& nav = s_controller_nav_states[controller_id];
if (is_dpad_h)
nav.dpad_h_held = (value > 0.0f);
else
nav.dpad_v_held = (value > 0.0f);
if (nav.dpad_h_held && nav.dpad_v_held)
return false;
}
MTGS::RunOnGSThread(
[key = key_map[static_cast<u32>(key)], value, layout]() mutable {
if (s_gamepad_swap_noth_west)
@@ -1234,6 +1273,81 @@ bool ImGuiManager::ProcessGenericInputEvent(GenericInputBinding key, InputLayout
return s_imgui_wants_keyboard.load(std::memory_order_acquire);
}
void ImGuiManager::ProcessGenericAxisEvent(GenericInputBinding negative_key, GenericInputBinding positive_key, InputLayout layout, float value, u32 controller_id)
{
// Hysteresis prevents wobble: activate at 0.5, release at 0.2.
static constexpr float ACTIVATE_THRESHOLD = 0.5f;
static constexpr float RELEASE_THRESHOLD = 0.2f;
// Ignore diagonal input and track binary state per direction to suppress duplicate events.
using AxisState = ControllerNavState::AxisState;
ControllerNavState& nav = s_controller_nav_states[controller_id];
AxisState* state = nullptr;
bool is_x_axis = false;
if (negative_key == GenericInputBinding::LeftStickLeft)
{
state = &nav.left_stick;
state->x = value;
is_x_axis = true;
}
else if (negative_key == GenericInputBinding::LeftStickUp)
{
state = &nav.left_stick;
state->y = value;
is_x_axis = false;
}
else if (negative_key == GenericInputBinding::RightStickLeft)
{
state = &nav.right_stick;
state->x = value;
is_x_axis = true;
}
else if (negative_key == GenericInputBinding::RightStickUp)
{
state = &nav.right_stick;
state->y = value;
is_x_axis = false;
}
float suppressed_value = value;
if (state)
{
const float other = is_x_axis ? state->y : state->x;
if (std::abs(other) > std::abs(value))
suppressed_value = 0.0f;
}
// Treat as binary like the D-pad: either fully pressed or released, with a deadzone.
bool* neg_active_ptr = state ? (is_x_axis ? &state->x_neg_active : &state->y_neg_active) : nullptr;
bool* pos_active_ptr = state ? (is_x_axis ? &state->x_pos_active : &state->y_pos_active) : nullptr;
if (negative_key != GenericInputBinding::Unknown)
{
const bool currently_active = neg_active_ptr ? *neg_active_ptr : false;
const float threshold = currently_active ? RELEASE_THRESHOLD : ACTIVATE_THRESHOLD;
const bool active = suppressed_value < -threshold;
if (!neg_active_ptr || active != currently_active)
{
if (neg_active_ptr)
*neg_active_ptr = active;
ProcessGenericInputEvent(negative_key, layout, active ? 1.0f : 0.0f, controller_id);
}
}
if (positive_key != GenericInputBinding::Unknown)
{
const bool currently_active = pos_active_ptr ? *pos_active_ptr : false;
const float threshold = currently_active ? RELEASE_THRESHOLD : ACTIVATE_THRESHOLD;
const bool active = suppressed_value > threshold;
if (!pos_active_ptr || active != currently_active)
{
if (pos_active_ptr)
*pos_active_ptr = active;
ProcessGenericInputEvent(positive_key, layout, active ? 1.0f : 0.0f, controller_id);
}
}
}
void ImGuiManager::SwapGamepadNorthWest(bool value)
{
s_gamepad_swap_noth_west = value;
+4 -1
View File
@@ -105,7 +105,10 @@ namespace ImGuiManager
bool ProcessHostKeyEvent(InputBindingKey key, float value);
/// Called on the CPU thread when any input event fires. Allows imgui to take over controller navigation.
bool ProcessGenericInputEvent(GenericInputBinding key, InputLayout layout, float value);
bool ProcessGenericInputEvent(GenericInputBinding key, InputLayout layout, float value, u32 controller_id = 0);
/// Called on the CPU thread for a bidirectional analog axis event.
void ProcessGenericAxisEvent(GenericInputBinding negative_key, GenericInputBinding positive_key, InputLayout layout, float value, u32 controller_id = 0);
/// Called to swap North/West gamepad buttons within ImGui
void SwapGamepadNorthWest(bool value);
+1 -1
View File
@@ -912,7 +912,7 @@ __ri void ImGuiManager::DrawSettingsOverlay(float scale, float margin, float spa
if (GSConfig.HWAccurateAlphaTest)
APPEND("AAT ");
if (GSConfig.HWAA1)
APPEND("AA1 ");
+68 -10
View File
@@ -114,7 +114,8 @@ namespace InputManager
static void GenerateRelativeMouseEvents();
static bool DoEventHook(InputBindingKey key, float value);
static bool PreprocessEvent(InputBindingKey key, float value, GenericInputBinding generic_key);
static bool PreprocessEvent(InputBindingKey key, float value, GenericInputBinding generic_key,
GenericInputBinding axis_neg_key, GenericInputBinding axis_pos_key);
static bool ProcessEvent(InputBindingKey key, float value, bool skip_button_handlers);
template <typename T>
@@ -132,6 +133,13 @@ static BindingMap s_binding_map;
static VibrationBindingArray s_pad_vibration_array;
static std::mutex s_binding_map_write_lock;
// Reverse lookups for controller navigation; user bindings take priority over static defaults.
using ControllerButtonGenericMap = std::unordered_map<InputBindingKey, GenericInputBinding, InputBindingKeyHash>;
static ControllerButtonGenericMap s_controller_button_generic_map;
using ControllerAxisGenericMap = std::unordered_map<InputBindingKey, std::array<GenericInputBinding, 2>, InputBindingKeyHash>;
static ControllerAxisGenericMap s_controller_axis_generic_map;
// Hooks/intercepting (for setting bindings)
static std::mutex m_event_intercept_mutex;
static InputInterceptHook::Callback m_event_intercept_callback;
@@ -637,7 +645,7 @@ void InputManager::AddBindings(const std::vector<std::string>& bindings, const I
// INISettingsInterface, can just update directly
si.SetStringList(section, key, new_bindings);
si.Save();
}
}
else
{
// LayeredSettingsInterface, Need to find which layer our binding came from
@@ -905,6 +913,32 @@ void InputManager::AddPadBindings(SettingsInterface& si, u32 pad_index, bool is_
Pad::SetControllerState(pad_index, bind_index, ApplySingleBindingScale(sensitivity, deadzone, value));
}},
bi.bind_type, si, section.c_str(), bi.name, is_profile);
// Build reverse maps for controller navigation; user bindings take priority over static defaults.
if (bi.generic_mapping != GenericInputBinding::Unknown)
{
for (const std::string& binding_str : bindings)
{
InputBindingKey bkey;
InputSource* bsrc;
if (!ParseBindingAndGetSource(binding_str, &bkey, &bsrc))
continue;
if (bkey.source_subtype == InputSubclass::ControllerButton)
{
s_controller_button_generic_map.emplace(bkey.MaskDirection(), bi.generic_mapping);
}
else if (bkey.source_subtype == InputSubclass::ControllerAxis)
{
auto& entry = s_controller_axis_generic_map[bkey.MaskDirection()];
// Negate modifier = negative half of axis (e.g. "-Axis0" → LeftStickLeft)
if (bkey.modifier == InputModifier::Negate)
entry[0] = bi.generic_mapping;
else
entry[1] = bi.generic_mapping;
}
}
}
}
}
break;
@@ -1071,13 +1105,14 @@ bool InputManager::IsAxisHandler(const InputEventHandler& handler)
return std::holds_alternative<InputAxisEventHandler>(handler);
}
bool InputManager::InvokeEvents(InputBindingKey key, float value, GenericInputBinding generic_key)
bool InputManager::InvokeEvents(InputBindingKey key, float value, GenericInputBinding generic_key,
GenericInputBinding axis_neg_key, GenericInputBinding axis_pos_key)
{
if (DoEventHook(key, value))
return true;
// If imgui ate the event, don't fire our handlers.
const bool skip_button_handlers = PreprocessEvent(key, value, generic_key);
const bool skip_button_handlers = PreprocessEvent(key, value, generic_key, axis_neg_key, axis_pos_key);
return ProcessEvent(key, value, skip_button_handlers);
}
@@ -1258,7 +1293,8 @@ void InputManager::ClearBindStateFromSource(InputBindingKey key)
} while (matched);
}
bool InputManager::PreprocessEvent(InputBindingKey key, float value, GenericInputBinding generic_key)
bool InputManager::PreprocessEvent(InputBindingKey key, float value, GenericInputBinding generic_key,
GenericInputBinding axis_neg_key, GenericInputBinding axis_pos_key)
{
// does imgui want the event?
if (key.source_type == InputSourceType::Keyboard)
@@ -1274,11 +1310,31 @@ bool InputManager::PreprocessEvent(InputBindingKey key, float value, GenericInpu
if (ImGuiManager::ProcessPointerButtonEvent(key, value))
return true;
}
else if (generic_key != GenericInputBinding::Unknown)
else if (key.source_subtype == InputSubclass::ControllerButton)
{
InputLayout layout = s_input_sources[static_cast<u32>(InputSourceType::SDL)]->GetControllerLayout(key.source_index);
if (ImGuiManager::ProcessGenericInputEvent(generic_key, layout, value) && value != 0.0f)
return true;
// User binding takes priority; fall back to the generic_key passed by the source (static table).
const auto it = s_controller_button_generic_map.find(key.MaskDirection());
const GenericInputBinding resolved = (it != s_controller_button_generic_map.end()) ? it->second : generic_key;
if (resolved != GenericInputBinding::Unknown)
{
const u32 controller_id = (static_cast<u32>(key.source_type) << 8) | key.source_index;
const InputLayout layout = s_input_sources[static_cast<u32>(InputSourceType::SDL)]->GetControllerLayout(key.source_index);
if (ImGuiManager::ProcessGenericInputEvent(resolved, layout, value, controller_id) && value != 0.0f)
return true;
}
}
else if (key.source_subtype == InputSubclass::ControllerAxis)
{
// User binding takes priority; fall back to the neg/pos keys passed by the source (static table).
const auto it = s_controller_axis_generic_map.find(key.MaskDirection());
const GenericInputBinding neg = (it != s_controller_axis_generic_map.end()) ? it->second[0] : axis_neg_key;
const GenericInputBinding pos = (it != s_controller_axis_generic_map.end()) ? it->second[1] : axis_pos_key;
if (neg != GenericInputBinding::Unknown || pos != GenericInputBinding::Unknown)
{
const u32 controller_id = (static_cast<u32>(key.source_type) << 8) | key.source_index;
const InputLayout layout = s_input_sources[static_cast<u32>(InputSourceType::SDL)]->GetControllerLayout(key.source_index);
ImGuiManager::ProcessGenericAxisEvent(neg, pos, layout, value, controller_id);
}
}
return false;
@@ -1557,6 +1613,8 @@ void InputManager::ReloadBindings(SettingsInterface& si, SettingsInterface& bind
s_pad_vibration_array.clear();
s_keyboard_event_callbacks.clear();
s_pointer_move_callbacks.clear();
s_controller_button_generic_map.clear();
s_controller_axis_generic_map.clear();
// Hotkeys use the base configuration, except if the custom hotkeys option is enabled.
AddHotkeyBindings(hotkey_binding_si, is_hotkey_profile);
@@ -1571,7 +1629,7 @@ void InputManager::ReloadBindings(SettingsInterface& si, SettingsInterface& bind
for (u32 axis = 0; axis <= static_cast<u32>(InputPointerAxis::Y); axis++)
{
s_pointer_axis_speed[axis] = si.GetFloatValue("Pad", fmt::format("Pointer{}Speed", s_pointer_axis_setting_names[axis]).c_str(), 40.0f) /
ui_ctrl_range * pointer_sensitivity;
ui_ctrl_range * pointer_sensitivity;
s_pointer_axis_dead_zone[axis] = std::min(
si.GetFloatValue("Pad", fmt::format("Pointer{}DeadZone", s_pointer_axis_setting_names[axis]).c_str(), 20.0f) / ui_ctrl_range, 1.0f);
s_pointer_axis_range[axis] = 1.0f - s_pointer_axis_dead_zone[axis];
+3 -1
View File
@@ -271,7 +271,9 @@ namespace InputManager
/// Updates internal state for any binds for this key, and fires callbacks as needed.
/// Returns true if anything was bound to this key, otherwise false.
bool InvokeEvents(InputBindingKey key, float value, GenericInputBinding generic_key = GenericInputBinding::Unknown);
bool InvokeEvents(InputBindingKey key, float value, GenericInputBinding generic_key = GenericInputBinding::Unknown,
GenericInputBinding axis_neg_key = GenericInputBinding::Unknown,
GenericInputBinding axis_pos_key = GenericInputBinding::Unknown);
/// Clears internal state for any binds with a matching source/index.
void ClearBindStateFromSource(InputBindingKey key);
+17 -5
View File
@@ -1489,7 +1489,19 @@ bool SDLInputSource::HandleGamepadAxisEvent(const SDL_GamepadAxisEvent* ev)
return false;
const InputBindingKey key(MakeGenericControllerAxisKey(InputSourceType::SDL, it->player_id, ev->axis));
InputManager::InvokeEvents(key, NormalizeS16(ev->value));
const float value = NormalizeS16(ev->value);
if (ev->axis < std::size(s_sdl_generic_binding_axis_mapping))
{
InputManager::InvokeEvents(key, value, GenericInputBinding::Unknown,
s_sdl_generic_binding_axis_mapping[ev->axis][0],
s_sdl_generic_binding_axis_mapping[ev->axis][1]);
}
else
{
InputManager::InvokeEvents(key, value);
}
return true;
}
@@ -1501,8 +1513,8 @@ bool SDLInputSource::HandleGamepadButtonEvent(const SDL_GamepadButtonEvent* ev)
const InputBindingKey key(MakeGenericControllerButtonKey(InputSourceType::SDL, it->player_id, ev->button));
const GenericInputBinding generic_key = (ev->button < std::size(s_sdl_generic_binding_button_mapping)) ?
s_sdl_generic_binding_button_mapping[ev->button] :
GenericInputBinding::Unknown;
s_sdl_generic_binding_button_mapping[ev->button] :
GenericInputBinding::Unknown;
InputManager::InvokeEvents(key, static_cast<float>(ev->down), generic_key);
return true;
}
@@ -1765,6 +1777,6 @@ bool SDLInputSource::IsControllerSixaxis(const ControllerData& cd)
// This differing layout also isn't mapped correctly in SDL, I think due to how L2/R2 are exposed.
// Also see SetHints regarding reading the pressure sense from DsHidMini's SDF mode.
return type == SDL_GAMEPAD_TYPE_PS3 &&
SDL_GetNumJoystickAxes(cd.joystick) == 16 &&
SDL_GetNumJoystickButtons(cd.joystick) == 11;
SDL_GetNumJoystickAxes(cd.joystick) == 16 &&
SDL_GetNumJoystickButtons(cd.joystick) == 11;
}
+10 -3
View File
@@ -513,8 +513,11 @@ void XInputSource::CheckForStateChanges(u32 index, const XINPUT_STATE& new_state
#define CHECK_AXIS(field, axis, min_value, max_value) \
if (ogp.field != ngp.field) \
{ \
InputManager::InvokeEvents(MakeGenericControllerAxisKey(InputSourceType::XInput, index, axis), \
static_cast<float>(ngp.field) / ((ngp.field < 0) ? min_value : max_value)); \
const float value = static_cast<float>(ngp.field) / ((ngp.field < 0) ? min_value : max_value); \
InputManager::InvokeEvents(MakeGenericControllerAxisKey(InputSourceType::XInput, index, axis), value, \
GenericInputBinding::Unknown, \
s_xinput_generic_binding_axis_mapping[axis][0], \
s_xinput_generic_binding_axis_mapping[axis][1]); \
}
// Y axes is inverted in XInput when compared to SDL.
@@ -560,7 +563,11 @@ void XInputSource::CheckForStateChangesSCP(u32 index, const SCP_EXTN& new_state)
#define CHECK_AXIS(field, mult) \
if (ogp.field != ngp.field) \
{ \
InputManager::InvokeEvents(MakeGenericControllerAxisKey(InputSourceType::XInput, index, axis), ngp.field* mult); \
const float value = ngp.field * mult; \
InputManager::InvokeEvents(MakeGenericControllerAxisKey(InputSourceType::XInput, index, axis), value, \
GenericInputBinding::Unknown, \
s_xinput_generic_binding_axis_mapping[axis][0], \
s_xinput_generic_binding_axis_mapping[axis][1]); \
} \
axis++;
+12 -6
View File
@@ -150,12 +150,18 @@ void SPU2::UpdateSampleRate()
u32 SPU2::GetOutputVolume()
{
return s_output_stream->GetOutputVolume();
return s_output_stream ? s_output_stream->GetOutputVolume() : 0;
}
void SPU2::SetOutputVolume(u32 volume)
{
s_output_stream->SetOutputVolume(volume);
if (VMManager::GetTargetSpeed() == 1.0f)
s_standard_volume = volume;
else
s_fast_forward_volume = volume;
if (s_output_stream)
SPU2::UpdateOutputVolume();
}
float SPU2::GetNominalRate()
@@ -198,9 +204,9 @@ void SPU2::SaveOutputVolume()
if (!s_output_muted)
{
if (VMManager::GetTargetSpeed() == 1.0f)
s_standard_volume = s_output_stream->GetOutputVolume();
s_standard_volume = GetOutputVolume();
else
s_fast_forward_volume = s_output_stream->GetOutputVolume();
s_fast_forward_volume = GetOutputVolume();
}
}
@@ -268,12 +274,12 @@ void SPU2::OnTargetSpeedChanged()
{
if (VMManager::GetTargetSpeed() == 1.0f)
{
s_fast_forward_volume = s_output_stream->GetOutputVolume();
s_fast_forward_volume = GetOutputVolume();
s_output_stream->SetOutputVolume(s_standard_volume);
}
else
{
s_standard_volume = s_output_stream->GetOutputVolume();
s_standard_volume = GetOutputVolume();
s_output_stream->SetOutputVolume(s_fast_forward_volume);
}
}
+1 -1
View File
@@ -62,7 +62,7 @@
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
<ForcedIncludeFiles>PrecompiledHeader.h;%(ForcedIncludeFiles)</ForcedIncludeFiles>
<PreprocessorDefinitions>C4_NO_DEBUG_BREAK;ST_NO_EXCEPTION_HANDLING;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>C4_NO_DEBUG_BREAK;ST_NO_EXCEPTION_HANDLING;D3D12MA_USING_DIRECTX_HEADERS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(Platform)'!='ARM64'">ENABLE_RAINTEGRATION;ENABLE_OPENGL;ENABLE_VULKAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(Platform)'=='x64'">XBYAK_NO_EXCEPTION;ZYCORE_STATIC_BUILD;ZYDIS_STATIC_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(Platform)'=='ARM64'">VIXL_INCLUDE_TARGET_AARCH64;%(PreprocessorDefinitions)</PreprocessorDefinitions>