Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5310f0ca4 | ||
|
|
972fcfa0f0 | ||
|
|
b0582f6ea3 | ||
|
|
8b6c28b374 | ||
|
|
0a3c27765d | ||
|
|
040a8b8fc6 | ||
|
|
9f67f72d3c | ||
|
|
04438ace57 | ||
|
|
e16236e450 | ||
|
|
3807517e02 | ||
|
|
ab8bd8e754 | ||
|
|
65d63085f8 | ||
|
|
2c8ecc3ea7 | ||
|
|
5fbc79c466 | ||
|
|
8fe4183856 | ||
|
|
0d52608f1d | ||
|
|
5df3c54b84 | ||
|
|
b2db2e71b6 | ||
|
|
7978370b39 | ||
|
|
d187c61599 | ||
|
|
fa745a58b2 |
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 |
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <d3d11.h>
|
||||
#include <d3d12.h>
|
||||
#include <directx/d3d12.h>
|
||||
#include <d3dcompiler.h>
|
||||
#include <fstream>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "common/RedtapeWilCom.h"
|
||||
#include "common/Assertions.h"
|
||||
|
||||
#include <d3d12.h>
|
||||
#include <directx/d3d12.h>
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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]() {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -912,7 +912,7 @@ __ri void ImGuiManager::DrawSettingsOverlay(float scale, float margin, float spa
|
||||
|
||||
if (GSConfig.HWAccurateAlphaTest)
|
||||
APPEND("AAT ");
|
||||
|
||||
|
||||
if (GSConfig.HWAA1)
|
||||
APPEND("AA1 ");
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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++;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||