Move frontend-common to util/core
This commit is contained in:
+25
-5
@@ -16,6 +16,8 @@ add_library(core
|
||||
cheats.h
|
||||
controller.cpp
|
||||
controller.h
|
||||
common_host.cpp
|
||||
common_host.h
|
||||
cpu_code_cache.cpp
|
||||
cpu_code_cache.h
|
||||
cpu_core.cpp
|
||||
@@ -29,8 +31,12 @@ add_library(core
|
||||
digital_controller.h
|
||||
dma.cpp
|
||||
dma.h
|
||||
fullscreen_ui.cpp
|
||||
fullscreen_ui.h
|
||||
game_database.cpp
|
||||
game_database.h
|
||||
game_list.cpp
|
||||
game_list.h
|
||||
gdb_protocol.cpp
|
||||
gdb_protocol.h
|
||||
gpu.cpp
|
||||
@@ -54,12 +60,13 @@ add_library(core
|
||||
gte_types.h
|
||||
host.cpp
|
||||
host.h
|
||||
host_display.cpp
|
||||
host_display.h
|
||||
host_interface_progress_callback.cpp
|
||||
host_interface_progress_callback.h
|
||||
host_settings.cpp
|
||||
host_settings.h
|
||||
input_types.h
|
||||
imgui_overlays.cpp
|
||||
imgui_overlays.h
|
||||
interrupt_controller.cpp
|
||||
interrupt_controller.h
|
||||
libcrypt_serials.cpp
|
||||
@@ -90,8 +97,6 @@ add_library(core
|
||||
settings.cpp
|
||||
settings.h
|
||||
shader_cache_version.h
|
||||
shadergen.cpp
|
||||
shadergen.h
|
||||
sio.cpp
|
||||
sio.h
|
||||
spu.cpp
|
||||
@@ -181,5 +186,20 @@ else()
|
||||
endif()
|
||||
|
||||
if(ENABLE_CHEEVOS)
|
||||
target_compile_definitions(core PRIVATE -DWITH_CHEEVOS=1)
|
||||
target_sources(core PRIVATE
|
||||
achievements.cpp
|
||||
achievements_private.h
|
||||
)
|
||||
target_compile_definitions(core PUBLIC -DWITH_CHEEVOS=1)
|
||||
target_link_libraries(core PRIVATE rcheevos rapidjson)
|
||||
endif()
|
||||
|
||||
if(ENABLE_DISCORD_PRESENCE)
|
||||
target_compile_definitions(core PUBLIC -DWITH_DISCORD_PRESENCE=1)
|
||||
target_link_libraries(core PRIVATE discord-rpc)
|
||||
endif()
|
||||
|
||||
# Copy the provided data directory to the output directory.
|
||||
add_custom_command(TARGET core POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/data" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}"
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,8 @@
|
||||
#pragma once
|
||||
#include "common/types.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
class StateWrapper;
|
||||
class CDImage;
|
||||
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "achievements.h"
|
||||
#include "settings.h"
|
||||
#include "types.h"
|
||||
|
||||
#include "common/string.h"
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class CDImage;
|
||||
class StateWrapper;
|
||||
|
||||
namespace Achievements {
|
||||
enum class AchievementCategory : u8
|
||||
{
|
||||
Local = 0,
|
||||
Core = 3,
|
||||
Unofficial = 5
|
||||
};
|
||||
|
||||
struct Achievement
|
||||
{
|
||||
u32 id;
|
||||
std::string title;
|
||||
std::string description;
|
||||
std::string memaddr;
|
||||
std::string badge_name;
|
||||
|
||||
// badge paths are mutable because they're resolved when they're needed.
|
||||
mutable std::string locked_badge_path;
|
||||
mutable std::string unlocked_badge_path;
|
||||
|
||||
u32 points;
|
||||
AchievementCategory category;
|
||||
bool locked;
|
||||
bool active;
|
||||
bool primed;
|
||||
};
|
||||
|
||||
struct Leaderboard
|
||||
{
|
||||
u32 id;
|
||||
std::string title;
|
||||
std::string description;
|
||||
int format;
|
||||
};
|
||||
|
||||
struct LeaderboardEntry
|
||||
{
|
||||
std::string user;
|
||||
std::string formatted_score;
|
||||
time_t submitted;
|
||||
u32 rank;
|
||||
bool is_self;
|
||||
};
|
||||
|
||||
// RAIntegration only exists for Windows, so no point checking it on other platforms.
|
||||
#ifdef WITH_RAINTEGRATION
|
||||
|
||||
bool IsUsingRAIntegration();
|
||||
|
||||
#else
|
||||
|
||||
static ALWAYS_INLINE bool IsUsingRAIntegration()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool IsActive();
|
||||
bool IsLoggedIn();
|
||||
bool ChallengeModeActive();
|
||||
bool LeaderboardsActive();
|
||||
bool IsTestModeActive();
|
||||
bool IsUnofficialTestModeActive();
|
||||
bool IsRichPresenceEnabled();
|
||||
bool HasActiveGame();
|
||||
|
||||
u32 GetGameID();
|
||||
|
||||
/// Acquires the achievements lock. Must be held when accessing any achievement state from another thread.
|
||||
std::unique_lock<std::recursive_mutex> GetLock();
|
||||
|
||||
void Initialize();
|
||||
void UpdateSettings(const Settings& old_config);
|
||||
|
||||
/// Called when the system is being reset. If it returns false, the reset should be aborted.
|
||||
bool ConfirmSystemReset();
|
||||
|
||||
/// Called when the system is being shut down. If Shutdown() returns false, the shutdown should be aborted.
|
||||
bool Shutdown();
|
||||
|
||||
/// Called when the system is being paused and resumed.
|
||||
void OnSystemPaused(bool paused);
|
||||
|
||||
/// Called once a frame at vsync time on the CPU thread.
|
||||
void FrameUpdate();
|
||||
|
||||
/// Called when the system is paused, because FrameUpdate() won't be getting called.
|
||||
void ProcessPendingHTTPRequests();
|
||||
|
||||
/// Saves/loads state.
|
||||
bool DoState(StateWrapper& sw);
|
||||
|
||||
/// Returns true if the current game has any achievements or leaderboards.
|
||||
/// Does not need to have the lock held.
|
||||
bool SafeHasAchievementsOrLeaderboards();
|
||||
|
||||
const std::string& GetUsername();
|
||||
const std::string& GetRichPresenceString();
|
||||
|
||||
bool LoginAsync(const char* username, const char* password);
|
||||
bool Login(const char* username, const char* password);
|
||||
void Logout();
|
||||
|
||||
void GameChanged(const std::string& path, CDImage* image);
|
||||
|
||||
/// Re-enables hardcode mode if it is enabled in the settings.
|
||||
bool ResetChallengeMode();
|
||||
|
||||
/// Forces hardcore mode off until next reset.
|
||||
void DisableChallengeMode();
|
||||
|
||||
/// Prompts the user to disable hardcore mode, if they agree, returns true.
|
||||
bool ConfirmChallengeModeDisable(const char* trigger);
|
||||
|
||||
/// Returns true if features such as save states should be disabled.
|
||||
bool ChallengeModeActive();
|
||||
|
||||
const std::string& GetGameTitle();
|
||||
const std::string& GetGameIcon();
|
||||
|
||||
bool EnumerateAchievements(std::function<bool(const Achievement&)> callback);
|
||||
u32 GetUnlockedAchiementCount();
|
||||
u32 GetAchievementCount();
|
||||
u32 GetMaximumPointsForGame();
|
||||
u32 GetCurrentPointsForGame();
|
||||
|
||||
bool EnumerateLeaderboards(std::function<bool(const Leaderboard&)> callback);
|
||||
std::optional<bool> TryEnumerateLeaderboardEntries(u32 id, std::function<bool(const LeaderboardEntry&)> callback);
|
||||
const Leaderboard* GetLeaderboardByID(u32 id);
|
||||
u32 GetLeaderboardCount();
|
||||
bool IsLeaderboardTimeType(const Leaderboard& leaderboard);
|
||||
u32 GetPrimedAchievementCount();
|
||||
|
||||
const Achievement* GetAchievementByID(u32 id);
|
||||
std::pair<u32, u32> GetAchievementProgress(const Achievement& achievement);
|
||||
TinyString GetAchievementProgressText(const Achievement& achievement);
|
||||
const std::string& GetAchievementBadgePath(const Achievement& achievement, bool download_if_missing = true,
|
||||
bool force_unlocked_icon = false);
|
||||
std::string GetAchievementBadgeURL(const Achievement& achievement);
|
||||
|
||||
#ifdef WITH_RAINTEGRATION
|
||||
void SwitchToRAIntegration();
|
||||
|
||||
namespace RAIntegration {
|
||||
void MainWindowChanged(void* new_handle);
|
||||
void GameChanged();
|
||||
std::vector<std::tuple<int, std::string, bool>> GetMenuItems();
|
||||
void ActivateMenuItem(int item);
|
||||
} // namespace RAIntegration
|
||||
#endif
|
||||
} // namespace Achievements
|
||||
|
||||
/// Functions implemented in the frontend.
|
||||
namespace Host {
|
||||
void OnAchievementsRefreshed();
|
||||
void OnAchievementsChallengeModeChanged();
|
||||
} // namespace Host
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "system.h"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class SettingsInterface;
|
||||
|
||||
class AudioStream;
|
||||
enum class AudioStretchMode : u8;
|
||||
|
||||
namespace CommonHost {
|
||||
/// Initializes configuration.
|
||||
void UpdateLogSettings();
|
||||
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
|
||||
void SetDefaultSettings(SettingsInterface& si);
|
||||
void SetDefaultControllerSettings(SettingsInterface& si);
|
||||
void SetDefaultHotkeyBindings(SettingsInterface& si);
|
||||
void LoadSettings(SettingsInterface& si, std::unique_lock<std::mutex>& lock);
|
||||
void CheckForSettingsChanges(const Settings& old_settings);
|
||||
void OnSystemStarting();
|
||||
void OnSystemStarted();
|
||||
void OnSystemDestroyed();
|
||||
void OnSystemPaused();
|
||||
void OnSystemResumed();
|
||||
void OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name);
|
||||
void PumpMessagesOnCPUThread();
|
||||
bool CreateHostDisplayResources();
|
||||
void ReleaseHostDisplayResources();
|
||||
|
||||
/// Returns the time elapsed in the current play session.
|
||||
u64 GetSessionPlayedTime();
|
||||
|
||||
} // namespace CommonHost
|
||||
|
||||
namespace ImGuiManager {
|
||||
void RenderDebugWindows();
|
||||
}
|
||||
+5
-5
@@ -4,16 +4,16 @@
|
||||
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>WITH_CHEEVOS=1;WITH_CUBEB=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>WITH_CHEEVOS=1;WITH_DISCORD_PRESENCE=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="('$(Platform)'!='ARM64')">WITH_RAINTEGRATION=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="('$(Platform)'=='x64' Or '$(Platform)'=='ARM' Or '$(Platform)'=='ARM64')">WITH_RECOMPILER=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="('$(Platform)'=='x64' Or '$(Platform)'=='ARM64')">WITH_MMAP_FASTMEM=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\rcheevos\include;$(SolutionDir)dep\rapidjson\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories Condition="'$(Platform)'!='ARM64'">$(SolutionDir)dep\rainterface;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\rcheevos\include;$(SolutionDir)dep\rapidjson\include;$(SolutionDir)dep\discord-rpc\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories Condition="'$(Platform)'!='ARM64'">%(AdditionalIncludeDirectories);$(SolutionDir)dep\rainterface</AdditionalIncludeDirectories>
|
||||
|
||||
<AdditionalIncludeDirectories Condition="'$(Platform)'=='x64'">$(SolutionDir)dep\xbyak\xbyak;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories Condition="'$(Platform)'=='ARM' Or '$(Platform)'=='ARM64'">$(SolutionDir)dep\vixl\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories Condition="'$(Platform)'=='x64'">%(AdditionalIncludeDirectories);$(SolutionDir)dep\xbyak\xbyak</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories Condition="'$(Platform)'=='ARM' Or '$(Platform)'=='ARM64'">%(AdditionalIncludeDirectories);$(SolutionDir)dep\vixl\include</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
</Project>
|
||||
|
||||
+17
-4
@@ -2,6 +2,7 @@
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\dep\msvc\vsprops\Configurations.props" />
|
||||
<ItemGroup>
|
||||
<ClCompile Include="achievements.cpp" />
|
||||
<ClCompile Include="analog_controller.cpp" />
|
||||
<ClCompile Include="analog_joystick.cpp" />
|
||||
<ClCompile Include="bios.cpp" />
|
||||
@@ -9,6 +10,7 @@
|
||||
<ClCompile Include="cdrom.cpp" />
|
||||
<ClCompile Include="cdrom_async_reader.cpp" />
|
||||
<ClCompile Include="cheats.cpp" />
|
||||
<ClCompile Include="common_host.cpp" />
|
||||
<ClCompile Include="cpu_core.cpp" />
|
||||
<ClCompile Include="cpu_disasm.cpp" />
|
||||
<ClCompile Include="cpu_code_cache.cpp" />
|
||||
@@ -32,7 +34,9 @@
|
||||
</ClCompile>
|
||||
<ClCompile Include="cpu_types.cpp" />
|
||||
<ClCompile Include="digital_controller.cpp" />
|
||||
<ClCompile Include="fullscreen_ui.cpp" />
|
||||
<ClCompile Include="game_database.cpp" />
|
||||
<ClCompile Include="game_list.cpp" />
|
||||
<ClCompile Include="gpu_backend.cpp" />
|
||||
<ClCompile Include="gpu_commands.cpp" />
|
||||
<ClCompile Include="gpu_hw_d3d11.cpp" />
|
||||
@@ -52,8 +56,9 @@
|
||||
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="host.cpp" />
|
||||
<ClCompile Include="host_display.cpp" />
|
||||
<ClCompile Include="host_interface_progress_callback.cpp" />
|
||||
<ClCompile Include="host_settings.cpp" />
|
||||
<ClCompile Include="imgui_overlays.cpp" />
|
||||
<ClCompile Include="interrupt_controller.cpp" />
|
||||
<ClCompile Include="libcrypt_serials.cpp" />
|
||||
<ClCompile Include="mdec.cpp" />
|
||||
@@ -70,7 +75,6 @@
|
||||
<ClCompile Include="psf_loader.cpp" />
|
||||
<ClCompile Include="resources.cpp" />
|
||||
<ClCompile Include="settings.cpp" />
|
||||
<ClCompile Include="shadergen.cpp" />
|
||||
<ClCompile Include="sio.cpp" />
|
||||
<ClCompile Include="spu.cpp" />
|
||||
<ClCompile Include="system.cpp" />
|
||||
@@ -79,6 +83,7 @@
|
||||
<ClCompile Include="timing_event.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="achievements_private.h" />
|
||||
<ClInclude Include="analog_controller.h" />
|
||||
<ClInclude Include="analog_joystick.h" />
|
||||
<ClInclude Include="bios.h" />
|
||||
@@ -87,6 +92,7 @@
|
||||
<ClInclude Include="cdrom_async_reader.h" />
|
||||
<ClInclude Include="cheats.h" />
|
||||
<ClInclude Include="achievements.h" />
|
||||
<ClInclude Include="common_host.h" />
|
||||
<ClInclude Include="cpu_core.h" />
|
||||
<ClInclude Include="cpu_core_private.h" />
|
||||
<ClInclude Include="cpu_disasm.h" />
|
||||
@@ -104,7 +110,9 @@
|
||||
<ExcludedFromBuild Condition="'$(Platform)'=='Win32'">true</ExcludedFromBuild>
|
||||
</ClInclude>
|
||||
<ClInclude Include="digital_controller.h" />
|
||||
<ClInclude Include="fullscreen_ui.h" />
|
||||
<ClInclude Include="game_database.h" />
|
||||
<ClInclude Include="game_list.h" />
|
||||
<ClInclude Include="gpu_backend.h" />
|
||||
<ClInclude Include="gpu_hw_d3d11.h" />
|
||||
<ClInclude Include="gpu_hw_d3d12.h" />
|
||||
@@ -126,9 +134,9 @@
|
||||
</ClInclude>
|
||||
<ClInclude Include="gte_types.h" />
|
||||
<ClInclude Include="host.h" />
|
||||
<ClInclude Include="host_display.h" />
|
||||
<ClInclude Include="host_interface_progress_callback.h" />
|
||||
<ClInclude Include="host_settings.h" />
|
||||
<ClInclude Include="imgui_overlays.h" />
|
||||
<ClInclude Include="input_types.h" />
|
||||
<ClInclude Include="interrupt_controller.h" />
|
||||
<ClInclude Include="libcrypt_serials.h" />
|
||||
@@ -147,7 +155,6 @@
|
||||
<ClInclude Include="resources.h" />
|
||||
<ClInclude Include="save_state_version.h" />
|
||||
<ClInclude Include="settings.h" />
|
||||
<ClInclude Include="shadergen.h" />
|
||||
<ClInclude Include="shader_cache_version.h" />
|
||||
<ClInclude Include="sio.h" />
|
||||
<ClInclude Include="spu.h" />
|
||||
@@ -158,6 +165,9 @@
|
||||
<ClInclude Include="types.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\dep\discord-rpc\discord-rpc.vcxproj">
|
||||
<Project>{4266505b-dbaf-484b-ab31-b53b9c8235b3}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\dep\imgui\imgui.vcxproj">
|
||||
<Project>{bb08260f-6fbc-46af-8924-090ee71360c6}</Project>
|
||||
</ProjectReference>
|
||||
@@ -185,6 +195,9 @@
|
||||
<ProjectReference Include="..\..\dep\zstd\zstd.vcxproj">
|
||||
<Project>{73ee0c55-6ffe-44e7-9c12-baa52434a797}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\scmversion\scmversion.vcxproj">
|
||||
<Project>{075ced82-6a20-46df-94c7-9624ac9ddbeb}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\util\util.vcxproj">
|
||||
<Project>{57f6206d-f264-4b07-baf8-11b9bbe1f455}</Project>
|
||||
</ProjectReference>
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
<ClCompile Include="sio.cpp" />
|
||||
<ClCompile Include="controller.cpp" />
|
||||
<ClCompile Include="analog_controller.cpp" />
|
||||
<ClCompile Include="host_display.cpp" />
|
||||
<ClCompile Include="timing_event.cpp" />
|
||||
<ClCompile Include="cdrom_async_reader.cpp" />
|
||||
<ClCompile Include="psf_loader.cpp" />
|
||||
@@ -47,7 +46,6 @@
|
||||
<ClCompile Include="host_interface_progress_callback.cpp" />
|
||||
<ClCompile Include="pgxp.cpp" />
|
||||
<ClCompile Include="cheats.cpp" />
|
||||
<ClCompile Include="shadergen.cpp" />
|
||||
<ClCompile Include="memory_card_image.cpp" />
|
||||
<ClCompile Include="analog_joystick.cpp" />
|
||||
<ClCompile Include="cpu_recompiler_code_generator_aarch32.cpp" />
|
||||
@@ -60,6 +58,12 @@
|
||||
<ClCompile Include="host.cpp" />
|
||||
<ClCompile Include="game_database.cpp" />
|
||||
<ClCompile Include="pcdrv.cpp" />
|
||||
<ClCompile Include="game_list.cpp" />
|
||||
<ClCompile Include="host_settings.cpp" />
|
||||
<ClCompile Include="imgui_overlays.cpp" />
|
||||
<ClCompile Include="fullscreen_ui.cpp" />
|
||||
<ClCompile Include="common_host.cpp" />
|
||||
<ClCompile Include="achievements.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="types.h" />
|
||||
@@ -86,7 +90,6 @@
|
||||
<ClInclude Include="gpu_sw.h" />
|
||||
<ClInclude Include="gpu_hw_shadergen.h" />
|
||||
<ClInclude Include="gpu_hw_d3d11.h" />
|
||||
<ClInclude Include="host_display.h" />
|
||||
<ClInclude Include="bios.h" />
|
||||
<ClInclude Include="cpu_recompiler_types.h" />
|
||||
<ClInclude Include="cpu_code_cache.h" />
|
||||
@@ -109,7 +112,6 @@
|
||||
<ClInclude Include="pgxp.h" />
|
||||
<ClInclude Include="cpu_core_private.h" />
|
||||
<ClInclude Include="cheats.h" />
|
||||
<ClInclude Include="shadergen.h" />
|
||||
<ClInclude Include="memory_card_image.h" />
|
||||
<ClInclude Include="analog_joystick.h" />
|
||||
<ClInclude Include="gpu_types.h" />
|
||||
@@ -127,5 +129,10 @@
|
||||
<ClInclude Include="game_database.h" />
|
||||
<ClInclude Include="input_types.h" />
|
||||
<ClInclude Include="pcdrv.h" />
|
||||
<ClInclude Include="game_list.h" />
|
||||
<ClInclude Include="imgui_overlays.h" />
|
||||
<ClInclude Include="fullscreen_ui.h" />
|
||||
<ClInclude Include="common_host.h" />
|
||||
<ClInclude Include="achievements_private.h" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#pragma once
|
||||
#include "common/progress_callback.h"
|
||||
#include "common/types.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class GPUTexture;
|
||||
|
||||
struct Settings;
|
||||
|
||||
namespace FullscreenUI {
|
||||
bool Initialize();
|
||||
bool IsInitialized();
|
||||
bool HasActiveWindow();
|
||||
void CheckForConfigChanges(const Settings& old_settings);
|
||||
void OnSystemStarted();
|
||||
void OnSystemPaused();
|
||||
void OnSystemResumed();
|
||||
void OnSystemDestroyed();
|
||||
void OnRunningGameChanged();
|
||||
void OpenPauseMenu();
|
||||
bool OpenAchievementsWindow();
|
||||
bool OpenLeaderboardsWindow();
|
||||
|
||||
void Shutdown();
|
||||
void Render();
|
||||
void InvalidateCoverCache();
|
||||
|
||||
// Returns true if the message has been dismissed.
|
||||
bool DrawErrorWindow(const char* message);
|
||||
bool DrawConfirmWindow(const char* message, bool* result);
|
||||
|
||||
class ProgressCallback final : public BaseProgressCallback
|
||||
{
|
||||
public:
|
||||
ProgressCallback(std::string name);
|
||||
~ProgressCallback() override;
|
||||
|
||||
ALWAYS_INLINE const std::string& GetName() const { return m_name; }
|
||||
|
||||
void PushState() override;
|
||||
void PopState() override;
|
||||
|
||||
void SetCancellable(bool cancellable) override;
|
||||
void SetTitle(const char* title) override;
|
||||
void SetStatusText(const char* text) override;
|
||||
void SetProgressRange(u32 range) override;
|
||||
void SetProgressValue(u32 value) override;
|
||||
|
||||
void DisplayError(const char* message) override;
|
||||
void DisplayWarning(const char* message) override;
|
||||
void DisplayInformation(const char* message) override;
|
||||
void DisplayDebugMessage(const char* message) override;
|
||||
|
||||
void ModalError(const char* message) override;
|
||||
bool ModalConfirmation(const char* message) override;
|
||||
void ModalInformation(const char* message) override;
|
||||
|
||||
void SetCancelled();
|
||||
|
||||
private:
|
||||
void Redraw(bool force);
|
||||
|
||||
std::string m_name;
|
||||
int m_last_progress_percent = -1;
|
||||
};
|
||||
} // namespace FullscreenUI
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,118 @@
|
||||
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "game_database.h"
|
||||
#include "types.h"
|
||||
|
||||
#include "util/cd_image.h"
|
||||
|
||||
#include "common/string.h"
|
||||
|
||||
#include <ctime>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
class ByteStream;
|
||||
class ProgressCallback;
|
||||
|
||||
struct SystemBootParameters;
|
||||
|
||||
namespace GameList {
|
||||
enum class EntryType
|
||||
{
|
||||
Disc,
|
||||
PSExe,
|
||||
Playlist,
|
||||
PSF,
|
||||
Count
|
||||
};
|
||||
|
||||
struct Entry
|
||||
{
|
||||
EntryType type = EntryType::Disc;
|
||||
DiscRegion region = DiscRegion::Other;
|
||||
|
||||
std::string path;
|
||||
std::string serial;
|
||||
std::string title;
|
||||
std::string genre;
|
||||
std::string publisher;
|
||||
std::string developer;
|
||||
u64 hash = 0;
|
||||
u64 total_size = 0;
|
||||
std::time_t last_modified_time = 0;
|
||||
std::time_t last_played_time = 0;
|
||||
std::time_t total_played_time = 0;
|
||||
|
||||
u64 release_date = 0;
|
||||
u32 supported_controllers = ~static_cast<u32>(0);
|
||||
u8 min_players = 1;
|
||||
u8 max_players = 1;
|
||||
u8 min_blocks = 0;
|
||||
u8 max_blocks = 0;
|
||||
|
||||
GameDatabase::CompatibilityRating compatibility = GameDatabase::CompatibilityRating::Unknown;
|
||||
|
||||
size_t GetReleaseDateString(char* buffer, size_t buffer_size) const;
|
||||
|
||||
ALWAYS_INLINE bool IsDisc() const { return (type == EntryType::Disc); }
|
||||
};
|
||||
|
||||
const char* GetEntryTypeName(EntryType type);
|
||||
const char* GetEntryTypeDisplayName(EntryType type);
|
||||
|
||||
bool IsScannableFilename(const std::string_view& path);
|
||||
|
||||
/// Populates a game list entry struct with information from the iso/elf.
|
||||
/// Do *not* call while the system is running, it will mess with CDVD state.
|
||||
bool PopulateEntryFromPath(const std::string& path, Entry* entry);
|
||||
|
||||
// Game list access. It's the caller's responsibility to hold the lock while manipulating the entry in any way.
|
||||
std::unique_lock<std::recursive_mutex> GetLock();
|
||||
const Entry* GetEntryByIndex(u32 index);
|
||||
const Entry* GetEntryForPath(const char* path);
|
||||
const Entry* GetEntryBySerial(const std::string_view& serial);
|
||||
const Entry* GetEntryBySerialAndHash(const std::string_view& serial, u64 hash);
|
||||
u32 GetEntryCount();
|
||||
|
||||
bool IsGameListLoaded();
|
||||
|
||||
/// Populates the game list with files in the configured directories.
|
||||
/// If invalidate_cache is set, all files will be re-scanned.
|
||||
/// If only_cache is set, no new files will be scanned, only those present in the cache.
|
||||
void Refresh(bool invalidate_cache, bool only_cache = false, ProgressCallback* progress = nullptr);
|
||||
|
||||
/// Add played time for the specified serial.
|
||||
void AddPlayedTimeForSerial(const std::string& serial, std::time_t last_time, std::time_t add_time);
|
||||
void ClearPlayedTimeForSerial(const std::string& serial);
|
||||
|
||||
/// Returns the total time played for a game. Requires the game to be scanned in the list.
|
||||
std::time_t GetCachedPlayedTimeForSerial(const std::string& serial);
|
||||
|
||||
/// Formats a timestamp to something human readable (e.g. Today, Yesterday, 10/11/12).
|
||||
TinyString FormatTimestamp(std::time_t timestamp);
|
||||
|
||||
/// Formats a timespan to something human readable (e.g. 1h2m3s or 1 hour).
|
||||
TinyString FormatTimespan(std::time_t timespan, bool long_format = false);
|
||||
|
||||
std::string GetCoverImagePathForEntry(const Entry* entry);
|
||||
std::string GetCoverImagePath(const std::string& path, const std::string& serial, const std::string& title);
|
||||
std::string GetNewCoverImagePathForEntry(const Entry* entry, const char* new_filename, bool use_serial);
|
||||
|
||||
/// Downloads covers using the specified URL templates. By default, covers are saved by title, but this can be changed
|
||||
/// with the use_serial parameter. save_callback optionall takes the entry and the path the new cover is saved to.
|
||||
bool DownloadCovers(const std::vector<std::string>& url_templates, bool use_serial = false,
|
||||
ProgressCallback* progress = nullptr,
|
||||
std::function<void(const Entry*, std::string)> save_callback = {});
|
||||
}; // namespace GameList
|
||||
|
||||
namespace Host {
|
||||
/// Asynchronously starts refreshing the game list.
|
||||
void RefreshGameListAsync(bool invalidate_cache);
|
||||
|
||||
/// Cancels game list refresh, if there is one in progress.
|
||||
void CancelGameListRefresh();
|
||||
} // namespace Host
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "common/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "cpu_core.h"
|
||||
#include "frontend-common/common_host.h"
|
||||
#include "common_host.h"
|
||||
#include "system.h"
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
#include "common/string_util.h"
|
||||
#include "dma.h"
|
||||
#include "host.h"
|
||||
#include "host_display.h"
|
||||
#include "util/host_display.h"
|
||||
#include "imgui.h"
|
||||
#include "interrupt_controller.h"
|
||||
#include "settings.h"
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
#pragma once
|
||||
#include "common/heap_array.h"
|
||||
#include "gpu.h"
|
||||
#include "host_display.h"
|
||||
#include "util/host_display.h"
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "common/timer.h"
|
||||
#include "gpu_hw_shadergen.h"
|
||||
#include "gpu_sw_backend.h"
|
||||
#include "host_display.h"
|
||||
#include "util/host_display.h"
|
||||
#include "shader_cache_version.h"
|
||||
#include "system.h"
|
||||
#include "util/state_wrapper.h"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "common/scoped_guard.h"
|
||||
#include "common/timer.h"
|
||||
#include "gpu_hw_shadergen.h"
|
||||
#include "host_display.h"
|
||||
#include "util/host_display.h"
|
||||
#include "system.h"
|
||||
Log_SetChannel(GPU_HW_D3D12);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "common/timer.h"
|
||||
#include "gpu_hw_shadergen.h"
|
||||
#include "host.h"
|
||||
#include "host_display.h"
|
||||
#include "util/host_display.h"
|
||||
#include "shader_cache_version.h"
|
||||
#include "system.h"
|
||||
#include "texture_replacements.h"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
#include "gpu_hw.h"
|
||||
#include "shadergen.h"
|
||||
#include "util/shadergen.h"
|
||||
|
||||
class GPU_HW_ShaderGen : public ShaderGen
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include "common/vulkan/shader_cache.h"
|
||||
#include "common/vulkan/util.h"
|
||||
#include "gpu_hw_shadergen.h"
|
||||
#include "host_display.h"
|
||||
#include "util/host_display.h"
|
||||
#include "system.h"
|
||||
#include "util/state_wrapper.h"
|
||||
Log_SetChannel(GPU_HW_Vulkan);
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
#include "common/log.h"
|
||||
#include "common/make_array.h"
|
||||
#include "common/platform.h"
|
||||
#include "host_display.h"
|
||||
#include "util/host_display.h"
|
||||
#include "system.h"
|
||||
#include <algorithm>
|
||||
Log_SetChannel(GPU_SW);
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
#include "common/heap_array.h"
|
||||
#include "gpu.h"
|
||||
#include "gpu_sw_backend.h"
|
||||
#include "host_display.h"
|
||||
#include "util/host_display.h"
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/log.h"
|
||||
#include "gpu_sw_backend.h"
|
||||
#include "host_display.h"
|
||||
#include "util/host_display.h"
|
||||
#include "system.h"
|
||||
#include <algorithm>
|
||||
Log_SetChannel(GPU_SW_Backend);
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
#include "common/bitutils.h"
|
||||
#include "cpu_core.h"
|
||||
#include "cpu_core_private.h"
|
||||
#include "host_display.h"
|
||||
#include "util/host_display.h"
|
||||
#include "pgxp.h"
|
||||
#include "settings.h"
|
||||
#include "timing_event.h"
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
#include "common/log.h"
|
||||
#include "gpu.h"
|
||||
#include "host.h"
|
||||
#include "host_display.h"
|
||||
#include "util/host_display.h"
|
||||
#include "resources.h"
|
||||
#include "system.h"
|
||||
#include "util/state_wrapper.h"
|
||||
|
||||
@@ -1,702 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#include "host_display.h"
|
||||
#include "common/align.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/file_system.h"
|
||||
#include "common/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/timer.h"
|
||||
#include "settings.h"
|
||||
#include "stb_image.h"
|
||||
#include "stb_image_resize.h"
|
||||
#include "stb_image_write.h"
|
||||
#include <cerrno>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
Log_SetChannel(HostDisplay);
|
||||
|
||||
std::unique_ptr<HostDisplay> g_host_display;
|
||||
|
||||
HostDisplay::~HostDisplay() = default;
|
||||
|
||||
RenderAPI HostDisplay::GetPreferredAPI()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return RenderAPI::D3D11;
|
||||
#else
|
||||
return RenderAPI::OpenGL;
|
||||
#endif
|
||||
}
|
||||
|
||||
void HostDisplay::DestroyResources()
|
||||
{
|
||||
m_cursor_texture.reset();
|
||||
}
|
||||
|
||||
bool HostDisplay::UpdateTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 pitch)
|
||||
{
|
||||
void* map_ptr;
|
||||
u32 map_pitch;
|
||||
if (!BeginTextureUpdate(texture, width, height, &map_ptr, &map_pitch))
|
||||
return false;
|
||||
|
||||
StringUtil::StrideMemCpy(map_ptr, map_pitch, data, pitch, std::min(pitch, map_pitch), height);
|
||||
EndTextureUpdate(texture, x, y, width, height);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HostDisplay::ParseFullscreenMode(const std::string_view& mode, u32* width, u32* height, float* refresh_rate)
|
||||
{
|
||||
if (!mode.empty())
|
||||
{
|
||||
std::string_view::size_type sep1 = mode.find('x');
|
||||
if (sep1 != std::string_view::npos)
|
||||
{
|
||||
std::optional<u32> owidth = StringUtil::FromChars<u32>(mode.substr(0, sep1));
|
||||
sep1++;
|
||||
|
||||
while (sep1 < mode.length() && std::isspace(mode[sep1]))
|
||||
sep1++;
|
||||
|
||||
if (owidth.has_value() && sep1 < mode.length())
|
||||
{
|
||||
std::string_view::size_type sep2 = mode.find('@', sep1);
|
||||
if (sep2 != std::string_view::npos)
|
||||
{
|
||||
std::optional<u32> oheight = StringUtil::FromChars<u32>(mode.substr(sep1, sep2 - sep1));
|
||||
sep2++;
|
||||
|
||||
while (sep2 < mode.length() && std::isspace(mode[sep2]))
|
||||
sep2++;
|
||||
|
||||
if (oheight.has_value() && sep2 < mode.length())
|
||||
{
|
||||
std::optional<float> orefresh_rate = StringUtil::FromChars<float>(mode.substr(sep2));
|
||||
if (orefresh_rate.has_value())
|
||||
{
|
||||
*width = owidth.value();
|
||||
*height = oheight.value();
|
||||
*refresh_rate = orefresh_rate.value();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
*refresh_rate = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string HostDisplay::GetFullscreenModeString(u32 width, u32 height, float refresh_rate)
|
||||
{
|
||||
return StringUtil::StdStringFromFormat("%u x %u @ %f hz", width, height, refresh_rate);
|
||||
}
|
||||
|
||||
bool HostDisplay::UsesLowerLeftOrigin() const
|
||||
{
|
||||
const RenderAPI api = GetRenderAPI();
|
||||
return (api == RenderAPI::OpenGL || api == RenderAPI::OpenGLES);
|
||||
}
|
||||
|
||||
void HostDisplay::SetDisplayMaxFPS(float max_fps)
|
||||
{
|
||||
m_display_frame_interval = (max_fps > 0.0f) ? (1.0f / max_fps) : 0.0f;
|
||||
}
|
||||
|
||||
bool HostDisplay::ShouldSkipDisplayingFrame()
|
||||
{
|
||||
if (m_display_frame_interval == 0.0f)
|
||||
return false;
|
||||
|
||||
const u64 now = Common::Timer::GetCurrentValue();
|
||||
const double diff = Common::Timer::ConvertValueToSeconds(now - m_last_frame_displayed_time);
|
||||
if (diff < m_display_frame_interval)
|
||||
return true;
|
||||
|
||||
m_last_frame_displayed_time = now;
|
||||
return false;
|
||||
}
|
||||
|
||||
void HostDisplay::ThrottlePresentation()
|
||||
{
|
||||
const float throttle_rate = (m_window_info.surface_refresh_rate > 0.0f) ? m_window_info.surface_refresh_rate : 60.0f;
|
||||
|
||||
const u64 sleep_period = Common::Timer::ConvertNanosecondsToValue(1e+9f / static_cast<double>(throttle_rate));
|
||||
const u64 current_ts = Common::Timer::GetCurrentValue();
|
||||
|
||||
// Allow it to fall behind/run ahead up to 2*period. Sleep isn't that precise, plus we need to
|
||||
// allow time for the actual rendering.
|
||||
const u64 max_variance = sleep_period * 2;
|
||||
if (static_cast<u64>(std::abs(static_cast<s64>(current_ts - m_last_frame_displayed_time))) > max_variance)
|
||||
m_last_frame_displayed_time = current_ts + sleep_period;
|
||||
else
|
||||
m_last_frame_displayed_time += sleep_period;
|
||||
|
||||
Common::Timer::SleepUntil(m_last_frame_displayed_time, false);
|
||||
}
|
||||
|
||||
bool HostDisplay::GetHostRefreshRate(float* refresh_rate)
|
||||
{
|
||||
if (m_window_info.surface_refresh_rate > 0.0f)
|
||||
{
|
||||
*refresh_rate = m_window_info.surface_refresh_rate;
|
||||
return true;
|
||||
}
|
||||
|
||||
return WindowInfo::QueryRefreshRateForWindow(m_window_info, refresh_rate);
|
||||
}
|
||||
|
||||
bool HostDisplay::SetGPUTimingEnabled(bool enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float HostDisplay::GetAndResetAccumulatedGPUTime()
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
void HostDisplay::SetSoftwareCursor(std::unique_ptr<GPUTexture> texture, float scale /*= 1.0f*/)
|
||||
{
|
||||
m_cursor_texture = std::move(texture);
|
||||
m_cursor_texture_scale = scale;
|
||||
}
|
||||
|
||||
bool HostDisplay::SetSoftwareCursor(const void* pixels, u32 width, u32 height, u32 stride, float scale /*= 1.0f*/)
|
||||
{
|
||||
std::unique_ptr<GPUTexture> tex =
|
||||
CreateTexture(width, height, 1, 1, 1, GPUTexture::Format::RGBA8, pixels, stride, false);
|
||||
if (!tex)
|
||||
return false;
|
||||
|
||||
SetSoftwareCursor(std::move(tex), scale);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HostDisplay::SetSoftwareCursor(const char* path, float scale /*= 1.0f*/)
|
||||
{
|
||||
auto fp = FileSystem::OpenManagedCFile(path, "rb");
|
||||
if (!fp)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int width, height, file_channels;
|
||||
u8* pixel_data = stbi_load_from_file(fp.get(), &width, &height, &file_channels, 4);
|
||||
if (!pixel_data)
|
||||
{
|
||||
const char* error_reason = stbi_failure_reason();
|
||||
Log_ErrorPrintf("Failed to load image from '%s': %s", path, error_reason ? error_reason : "unknown error");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<GPUTexture> tex =
|
||||
CreateTexture(static_cast<u32>(width), static_cast<u32>(height), 1, 1, 1, GPUTexture::Format::RGBA8, pixel_data,
|
||||
sizeof(u32) * static_cast<u32>(width), false);
|
||||
stbi_image_free(pixel_data);
|
||||
if (!tex)
|
||||
return false;
|
||||
|
||||
Log_InfoPrintf("Loaded %dx%d image from '%s' for software cursor", width, height, path);
|
||||
SetSoftwareCursor(std::move(tex), scale);
|
||||
return true;
|
||||
}
|
||||
|
||||
void HostDisplay::ClearSoftwareCursor()
|
||||
{
|
||||
m_cursor_texture.reset();
|
||||
m_cursor_texture_scale = 1.0f;
|
||||
}
|
||||
|
||||
bool HostDisplay::IsUsingLinearFiltering() const
|
||||
{
|
||||
return g_settings.display_linear_filtering;
|
||||
}
|
||||
|
||||
void HostDisplay::CalculateDrawRect(s32 window_width, s32 window_height, float* out_left, float* out_top,
|
||||
float* out_width, float* out_height, float* out_left_padding,
|
||||
float* out_top_padding, float* out_scale, float* out_x_scale,
|
||||
bool apply_aspect_ratio /* = true */) const
|
||||
{
|
||||
const float window_ratio = static_cast<float>(window_width) / static_cast<float>(window_height);
|
||||
const float display_aspect_ratio = g_settings.display_stretch ? window_ratio : m_display_aspect_ratio;
|
||||
const float x_scale =
|
||||
apply_aspect_ratio ?
|
||||
(display_aspect_ratio / (static_cast<float>(m_display_width) / static_cast<float>(m_display_height))) :
|
||||
1.0f;
|
||||
const float display_width = g_settings.display_stretch_vertically ? static_cast<float>(m_display_width) :
|
||||
static_cast<float>(m_display_width) * x_scale;
|
||||
const float display_height = g_settings.display_stretch_vertically ? static_cast<float>(m_display_height) / x_scale :
|
||||
static_cast<float>(m_display_height);
|
||||
const float active_left = g_settings.display_stretch_vertically ? static_cast<float>(m_display_active_left) :
|
||||
static_cast<float>(m_display_active_left) * x_scale;
|
||||
const float active_top = g_settings.display_stretch_vertically ? static_cast<float>(m_display_active_top) / x_scale :
|
||||
static_cast<float>(m_display_active_top);
|
||||
const float active_width = g_settings.display_stretch_vertically ?
|
||||
static_cast<float>(m_display_active_width) :
|
||||
static_cast<float>(m_display_active_width) * x_scale;
|
||||
const float active_height = g_settings.display_stretch_vertically ?
|
||||
static_cast<float>(m_display_active_height) / x_scale :
|
||||
static_cast<float>(m_display_active_height);
|
||||
if (out_x_scale)
|
||||
*out_x_scale = x_scale;
|
||||
|
||||
// now fit it within the window
|
||||
float scale;
|
||||
if ((display_width / display_height) >= window_ratio)
|
||||
{
|
||||
// align in middle vertically
|
||||
scale = static_cast<float>(window_width) / display_width;
|
||||
if (g_settings.display_integer_scaling)
|
||||
scale = std::max(std::floor(scale), 1.0f);
|
||||
|
||||
if (out_left_padding)
|
||||
{
|
||||
if (g_settings.display_integer_scaling)
|
||||
*out_left_padding = std::max<float>((static_cast<float>(window_width) - display_width * scale) / 2.0f, 0.0f);
|
||||
else
|
||||
*out_left_padding = 0.0f;
|
||||
}
|
||||
if (out_top_padding)
|
||||
{
|
||||
switch (g_settings.display_alignment)
|
||||
{
|
||||
case DisplayAlignment::RightOrBottom:
|
||||
*out_top_padding = std::max<float>(static_cast<float>(window_height) - (display_height * scale), 0.0f);
|
||||
break;
|
||||
|
||||
case DisplayAlignment::Center:
|
||||
*out_top_padding =
|
||||
std::max<float>((static_cast<float>(window_height) - (display_height * scale)) / 2.0f, 0.0f);
|
||||
break;
|
||||
|
||||
case DisplayAlignment::LeftOrTop:
|
||||
default:
|
||||
*out_top_padding = 0.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// align in middle horizontally
|
||||
scale = static_cast<float>(window_height) / display_height;
|
||||
if (g_settings.display_integer_scaling)
|
||||
scale = std::max(std::floor(scale), 1.0f);
|
||||
|
||||
if (out_left_padding)
|
||||
{
|
||||
switch (g_settings.display_alignment)
|
||||
{
|
||||
case DisplayAlignment::RightOrBottom:
|
||||
*out_left_padding = std::max<float>(static_cast<float>(window_width) - (display_width * scale), 0.0f);
|
||||
break;
|
||||
|
||||
case DisplayAlignment::Center:
|
||||
*out_left_padding =
|
||||
std::max<float>((static_cast<float>(window_width) - (display_width * scale)) / 2.0f, 0.0f);
|
||||
break;
|
||||
|
||||
case DisplayAlignment::LeftOrTop:
|
||||
default:
|
||||
*out_left_padding = 0.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (out_top_padding)
|
||||
{
|
||||
if (g_settings.display_integer_scaling)
|
||||
*out_top_padding = std::max<float>((static_cast<float>(window_height) - (display_height * scale)) / 2.0f, 0.0f);
|
||||
else
|
||||
*out_top_padding = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
*out_width = active_width * scale;
|
||||
*out_height = active_height * scale;
|
||||
*out_left = active_left * scale;
|
||||
*out_top = active_top * scale;
|
||||
if (out_scale)
|
||||
*out_scale = scale;
|
||||
}
|
||||
|
||||
std::tuple<s32, s32, s32, s32> HostDisplay::CalculateDrawRect(s32 window_width, s32 window_height,
|
||||
bool apply_aspect_ratio /* = true */) const
|
||||
{
|
||||
float left, top, width, height, left_padding, top_padding;
|
||||
CalculateDrawRect(window_width, window_height, &left, &top, &width, &height, &left_padding, &top_padding, nullptr,
|
||||
nullptr, apply_aspect_ratio);
|
||||
|
||||
return std::make_tuple(static_cast<s32>(left + left_padding), static_cast<s32>(top + top_padding),
|
||||
static_cast<s32>(width), static_cast<s32>(height));
|
||||
}
|
||||
|
||||
std::tuple<s32, s32, s32, s32> HostDisplay::CalculateSoftwareCursorDrawRect() const
|
||||
{
|
||||
return CalculateSoftwareCursorDrawRect(m_mouse_position_x, m_mouse_position_y);
|
||||
}
|
||||
|
||||
std::tuple<s32, s32, s32, s32> HostDisplay::CalculateSoftwareCursorDrawRect(s32 cursor_x, s32 cursor_y) const
|
||||
{
|
||||
const float scale = m_window_info.surface_scale * m_cursor_texture_scale;
|
||||
const u32 cursor_extents_x = static_cast<u32>(static_cast<float>(m_cursor_texture->GetWidth()) * scale * 0.5f);
|
||||
const u32 cursor_extents_y = static_cast<u32>(static_cast<float>(m_cursor_texture->GetHeight()) * scale * 0.5f);
|
||||
|
||||
const s32 out_left = cursor_x - cursor_extents_x;
|
||||
const s32 out_top = cursor_y - cursor_extents_y;
|
||||
const s32 out_width = cursor_extents_x * 2u;
|
||||
const s32 out_height = cursor_extents_y * 2u;
|
||||
|
||||
return std::tie(out_left, out_top, out_width, out_height);
|
||||
}
|
||||
|
||||
std::tuple<float, float> HostDisplay::ConvertWindowCoordinatesToDisplayCoordinates(s32 window_x, s32 window_y,
|
||||
s32 window_width,
|
||||
s32 window_height) const
|
||||
{
|
||||
float left, top, width, height, left_padding, top_padding;
|
||||
float scale, x_scale;
|
||||
CalculateDrawRect(window_width, window_height, &left, &top, &width, &height, &left_padding, &top_padding, &scale,
|
||||
&x_scale);
|
||||
|
||||
// convert coordinates to active display region, then to full display region
|
||||
const float scaled_display_x = static_cast<float>(window_x) - left_padding;
|
||||
const float scaled_display_y = static_cast<float>(window_y) - top_padding;
|
||||
|
||||
// scale back to internal resolution
|
||||
const float display_x = scaled_display_x / scale / x_scale;
|
||||
const float display_y = scaled_display_y / scale;
|
||||
|
||||
return std::make_tuple(display_x, display_y);
|
||||
}
|
||||
|
||||
static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename, FileSystem::ManagedCFilePtr fp,
|
||||
bool clear_alpha, bool flip_y, u32 resize_width, u32 resize_height,
|
||||
std::vector<u32> texture_data, u32 texture_data_stride,
|
||||
GPUTexture::Format texture_format)
|
||||
{
|
||||
|
||||
const char* extension = std::strrchr(filename.c_str(), '.');
|
||||
if (!extension)
|
||||
{
|
||||
Log_ErrorPrintf("Unable to determine file extension for '%s'", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GPUTexture::ConvertTextureDataToRGBA8(width, height, texture_data, texture_data_stride, texture_format))
|
||||
return false;
|
||||
|
||||
if (clear_alpha)
|
||||
{
|
||||
for (u32& pixel : texture_data)
|
||||
pixel |= 0xFF000000;
|
||||
}
|
||||
|
||||
if (flip_y)
|
||||
GPUTexture::FlipTextureDataRGBA8(width, height, texture_data, texture_data_stride);
|
||||
|
||||
if (resize_width > 0 && resize_height > 0 && (resize_width != width || resize_height != height))
|
||||
{
|
||||
std::vector<u32> resized_texture_data(resize_width * resize_height);
|
||||
u32 resized_texture_stride = sizeof(u32) * resize_width;
|
||||
if (!stbir_resize_uint8(reinterpret_cast<u8*>(texture_data.data()), width, height, texture_data_stride,
|
||||
reinterpret_cast<u8*>(resized_texture_data.data()), resize_width, resize_height,
|
||||
resized_texture_stride, 4))
|
||||
{
|
||||
Log_ErrorPrintf("Failed to resize texture data from %ux%u to %ux%u", width, height, resize_width, resize_height);
|
||||
return false;
|
||||
}
|
||||
|
||||
width = resize_width;
|
||||
height = resize_height;
|
||||
texture_data = std::move(resized_texture_data);
|
||||
texture_data_stride = resized_texture_stride;
|
||||
}
|
||||
|
||||
const auto write_func = [](void* context, void* data, int size) {
|
||||
std::fwrite(data, 1, size, static_cast<std::FILE*>(context));
|
||||
};
|
||||
|
||||
bool result = false;
|
||||
if (StringUtil::Strcasecmp(extension, ".png") == 0)
|
||||
{
|
||||
result =
|
||||
(stbi_write_png_to_func(write_func, fp.get(), width, height, 4, texture_data.data(), texture_data_stride) != 0);
|
||||
}
|
||||
else if (StringUtil::Strcasecmp(extension, ".jpg") == 0)
|
||||
{
|
||||
result = (stbi_write_jpg_to_func(write_func, fp.get(), width, height, 4, texture_data.data(), 95) != 0);
|
||||
}
|
||||
else if (StringUtil::Strcasecmp(extension, ".tga") == 0)
|
||||
{
|
||||
result = (stbi_write_tga_to_func(write_func, fp.get(), width, height, 4, texture_data.data()) != 0);
|
||||
}
|
||||
else if (StringUtil::Strcasecmp(extension, ".bmp") == 0)
|
||||
{
|
||||
result = (stbi_write_bmp_to_func(write_func, fp.get(), width, height, 4, texture_data.data()) != 0);
|
||||
}
|
||||
|
||||
if (!result)
|
||||
{
|
||||
Log_ErrorPrintf("Unknown extension in filename '%s' or save error: '%s'", filename.c_str(), extension);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HostDisplay::WriteTextureToFile(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, std::string filename,
|
||||
bool clear_alpha /* = true */, bool flip_y /* = false */,
|
||||
u32 resize_width /* = 0 */, u32 resize_height /* = 0 */,
|
||||
bool compress_on_thread /* = false */)
|
||||
{
|
||||
std::vector<u32> texture_data(width * height);
|
||||
u32 texture_data_stride = Common::AlignUpPow2(GPUTexture::GetPixelSize(texture->GetFormat()) * width, 4);
|
||||
if (!DownloadTexture(texture, x, y, width, height, texture_data.data(), texture_data_stride))
|
||||
{
|
||||
Log_ErrorPrintf("Texture download failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto fp = FileSystem::OpenManagedCFile(filename.c_str(), "wb");
|
||||
if (!fp)
|
||||
{
|
||||
Log_ErrorPrintf("Can't open file '%s': errno %d", filename.c_str(), errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!compress_on_thread)
|
||||
{
|
||||
return CompressAndWriteTextureToFile(width, height, std::move(filename), std::move(fp), clear_alpha, flip_y,
|
||||
resize_width, resize_height, std::move(texture_data), texture_data_stride,
|
||||
texture->GetFormat());
|
||||
}
|
||||
|
||||
std::thread compress_thread(CompressAndWriteTextureToFile, width, height, std::move(filename), std::move(fp),
|
||||
clear_alpha, flip_y, resize_width, resize_height, std::move(texture_data),
|
||||
texture_data_stride, texture->GetFormat());
|
||||
compress_thread.detach();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HostDisplay::WriteDisplayTextureToFile(std::string filename, bool full_resolution /* = true */,
|
||||
bool apply_aspect_ratio /* = true */, bool compress_on_thread /* = false */)
|
||||
{
|
||||
if (!m_display_texture)
|
||||
return false;
|
||||
|
||||
s32 resize_width = 0;
|
||||
s32 resize_height = std::abs(m_display_texture_view_height);
|
||||
if (apply_aspect_ratio)
|
||||
{
|
||||
const float ss_width_scale = static_cast<float>(m_display_active_width) / static_cast<float>(m_display_width);
|
||||
const float ss_height_scale = static_cast<float>(m_display_active_height) / static_cast<float>(m_display_height);
|
||||
const float ss_aspect_ratio = m_display_aspect_ratio * ss_width_scale / ss_height_scale;
|
||||
resize_width = g_settings.display_stretch_vertically ?
|
||||
m_display_texture_view_width :
|
||||
static_cast<s32>(static_cast<float>(resize_height) * ss_aspect_ratio);
|
||||
resize_height = g_settings.display_stretch_vertically ?
|
||||
static_cast<s32>(static_cast<float>(resize_height) /
|
||||
(m_display_aspect_ratio /
|
||||
(static_cast<float>(m_display_width) / static_cast<float>(m_display_height)))) :
|
||||
resize_height;
|
||||
}
|
||||
else
|
||||
{
|
||||
resize_width = m_display_texture_view_width;
|
||||
}
|
||||
|
||||
if (!full_resolution)
|
||||
{
|
||||
const s32 resolution_scale = std::abs(m_display_texture_view_height) / m_display_active_height;
|
||||
resize_height /= resolution_scale;
|
||||
resize_width /= resolution_scale;
|
||||
}
|
||||
|
||||
if (resize_width <= 0 || resize_height <= 0)
|
||||
return false;
|
||||
|
||||
const bool flip_y = (m_display_texture_view_height < 0);
|
||||
s32 read_height = m_display_texture_view_height;
|
||||
s32 read_y = m_display_texture_view_y;
|
||||
if (flip_y)
|
||||
{
|
||||
read_height = -m_display_texture_view_height;
|
||||
read_y =
|
||||
(m_display_texture->GetHeight() - read_height) - (m_display_texture->GetHeight() - m_display_texture_view_y);
|
||||
}
|
||||
|
||||
return WriteTextureToFile(m_display_texture, m_display_texture_view_x, read_y, m_display_texture_view_width,
|
||||
read_height, std::move(filename), true, flip_y, static_cast<u32>(resize_width),
|
||||
static_cast<u32>(resize_height), compress_on_thread);
|
||||
}
|
||||
|
||||
bool HostDisplay::WriteDisplayTextureToBuffer(std::vector<u32>* buffer, u32 resize_width /* = 0 */,
|
||||
u32 resize_height /* = 0 */, bool clear_alpha /* = true */)
|
||||
{
|
||||
if (!m_display_texture)
|
||||
return false;
|
||||
|
||||
const bool flip_y = (m_display_texture_view_height < 0);
|
||||
s32 read_width = m_display_texture_view_width;
|
||||
s32 read_height = m_display_texture_view_height;
|
||||
s32 read_x = m_display_texture_view_x;
|
||||
s32 read_y = m_display_texture_view_y;
|
||||
if (flip_y)
|
||||
{
|
||||
read_height = -m_display_texture_view_height;
|
||||
read_y =
|
||||
(m_display_texture->GetHeight() - read_height) - (m_display_texture->GetHeight() - m_display_texture_view_y);
|
||||
}
|
||||
|
||||
u32 width = static_cast<u32>(read_width);
|
||||
u32 height = static_cast<u32>(read_height);
|
||||
std::vector<u32> texture_data(width * height);
|
||||
u32 texture_data_stride = Common::AlignUpPow2(m_display_texture->GetPixelSize() * width, 4);
|
||||
if (!DownloadTexture(m_display_texture, read_x, read_y, width, height, texture_data.data(), texture_data_stride))
|
||||
{
|
||||
Log_ErrorPrintf("Failed to download texture from GPU.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GPUTexture::ConvertTextureDataToRGBA8(width, height, texture_data, texture_data_stride,
|
||||
m_display_texture->GetFormat()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (clear_alpha)
|
||||
{
|
||||
for (u32& pixel : texture_data)
|
||||
pixel |= 0xFF000000;
|
||||
}
|
||||
|
||||
if (flip_y)
|
||||
{
|
||||
std::vector<u32> temp(width);
|
||||
for (u32 flip_row = 0; flip_row < (height / 2); flip_row++)
|
||||
{
|
||||
u32* top_ptr = &texture_data[flip_row * width];
|
||||
u32* bottom_ptr = &texture_data[((height - 1) - flip_row) * width];
|
||||
std::memcpy(temp.data(), top_ptr, texture_data_stride);
|
||||
std::memcpy(top_ptr, bottom_ptr, texture_data_stride);
|
||||
std::memcpy(bottom_ptr, temp.data(), texture_data_stride);
|
||||
}
|
||||
}
|
||||
|
||||
if (resize_width > 0 && resize_height > 0 && (resize_width != width || resize_height != height))
|
||||
{
|
||||
std::vector<u32> resized_texture_data(resize_width * resize_height);
|
||||
u32 resized_texture_stride = sizeof(u32) * resize_width;
|
||||
if (!stbir_resize_uint8(reinterpret_cast<u8*>(texture_data.data()), width, height, texture_data_stride,
|
||||
reinterpret_cast<u8*>(resized_texture_data.data()), resize_width, resize_height,
|
||||
resized_texture_stride, 4))
|
||||
{
|
||||
Log_ErrorPrintf("Failed to resize texture data from %ux%u to %ux%u", width, height, resize_width, resize_height);
|
||||
return false;
|
||||
}
|
||||
|
||||
width = resize_width;
|
||||
height = resize_height;
|
||||
*buffer = std::move(resized_texture_data);
|
||||
texture_data_stride = resized_texture_stride;
|
||||
}
|
||||
else
|
||||
{
|
||||
*buffer = texture_data;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HostDisplay::WriteScreenshotToFile(std::string filename, bool internal_resolution /* = false */,
|
||||
bool compress_on_thread /* = false */)
|
||||
{
|
||||
u32 width = m_window_info.surface_width;
|
||||
u32 height = m_window_info.surface_height;
|
||||
auto [draw_left, draw_top, draw_width, draw_height] = CalculateDrawRect(width, height);
|
||||
|
||||
if (internal_resolution && m_display_texture_view_width != 0 && m_display_texture_view_height != 0)
|
||||
{
|
||||
// If internal res, scale the computed draw rectangle to the internal res.
|
||||
// We re-use the draw rect because it's already been AR corrected.
|
||||
const float sar =
|
||||
static_cast<float>(m_display_texture_view_width) / static_cast<float>(m_display_texture_view_height);
|
||||
const float dar = static_cast<float>(draw_width) / static_cast<float>(draw_height);
|
||||
if (sar >= dar)
|
||||
{
|
||||
// stretch height, preserve width
|
||||
const float scale = static_cast<float>(m_display_texture_view_width) / static_cast<float>(draw_width);
|
||||
width = m_display_texture_view_width;
|
||||
height = static_cast<u32>(std::round(static_cast<float>(draw_height) * scale));
|
||||
}
|
||||
else
|
||||
{
|
||||
// stretch width, preserve height
|
||||
const float scale = static_cast<float>(m_display_texture_view_height) / static_cast<float>(draw_height);
|
||||
width = static_cast<u32>(std::round(static_cast<float>(draw_width) * scale));
|
||||
height = m_display_texture_view_height;
|
||||
}
|
||||
|
||||
// DX11 won't go past 16K texture size.
|
||||
constexpr u32 MAX_TEXTURE_SIZE = 16384;
|
||||
if (width > MAX_TEXTURE_SIZE)
|
||||
{
|
||||
height = static_cast<u32>(static_cast<float>(height) /
|
||||
(static_cast<float>(width) / static_cast<float>(MAX_TEXTURE_SIZE)));
|
||||
width = MAX_TEXTURE_SIZE;
|
||||
}
|
||||
if (height > MAX_TEXTURE_SIZE)
|
||||
{
|
||||
height = MAX_TEXTURE_SIZE;
|
||||
width = static_cast<u32>(static_cast<float>(width) /
|
||||
(static_cast<float>(height) / static_cast<float>(MAX_TEXTURE_SIZE)));
|
||||
}
|
||||
|
||||
// Remove padding, it's not part of the framebuffer.
|
||||
draw_left = 0;
|
||||
draw_top = 0;
|
||||
draw_width = static_cast<s32>(width);
|
||||
draw_height = static_cast<s32>(height);
|
||||
}
|
||||
if (width == 0 || height == 0)
|
||||
return false;
|
||||
|
||||
std::vector<u32> pixels;
|
||||
u32 pixels_stride;
|
||||
GPUTexture::Format pixels_format;
|
||||
if (!RenderScreenshot(width, height,
|
||||
Common::Rectangle<s32>::FromExtents(draw_left, draw_top, draw_width, draw_height), &pixels,
|
||||
&pixels_stride, &pixels_format))
|
||||
{
|
||||
Log_ErrorPrintf("Failed to render %ux%u screenshot", width, height);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto fp = FileSystem::OpenManagedCFile(filename.c_str(), "wb");
|
||||
if (!fp)
|
||||
{
|
||||
Log_ErrorPrintf("Can't open file '%s': errno %d", filename.c_str(), errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!compress_on_thread)
|
||||
{
|
||||
return CompressAndWriteTextureToFile(width, height, std::move(filename), std::move(fp), true, UsesLowerLeftOrigin(),
|
||||
width, height, std::move(pixels), pixels_stride, pixels_format);
|
||||
}
|
||||
|
||||
std::thread compress_thread(CompressAndWriteTextureToFile, width, height, std::move(filename), std::move(fp), true,
|
||||
UsesLowerLeftOrigin(), width, height, std::move(pixels), pixels_stride, pixels_format);
|
||||
compress_thread.detach();
|
||||
return true;
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#pragma once
|
||||
#include "common/gpu_texture.h"
|
||||
#include "common/rectangle.h"
|
||||
#include "common/window_info.h"
|
||||
#include "types.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
enum class RenderAPI : u32
|
||||
{
|
||||
None,
|
||||
D3D11,
|
||||
D3D12,
|
||||
Vulkan,
|
||||
OpenGL,
|
||||
OpenGLES
|
||||
};
|
||||
|
||||
// Interface to the frontend's renderer.
|
||||
class HostDisplay
|
||||
{
|
||||
public:
|
||||
struct AdapterAndModeList
|
||||
{
|
||||
std::vector<std::string> adapter_names;
|
||||
std::vector<std::string> fullscreen_modes;
|
||||
};
|
||||
|
||||
virtual ~HostDisplay();
|
||||
|
||||
/// Returns the default/preferred API for the system.
|
||||
static RenderAPI GetPreferredAPI();
|
||||
|
||||
/// Parses a fullscreen mode into its components (width * height @ refresh hz)
|
||||
static bool ParseFullscreenMode(const std::string_view& mode, u32* width, u32* height, float* refresh_rate);
|
||||
|
||||
/// Converts a fullscreen mode to a string.
|
||||
static std::string GetFullscreenModeString(u32 width, u32 height, float refresh_rate);
|
||||
|
||||
ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_window_info; }
|
||||
ALWAYS_INLINE s32 GetWindowWidth() const { return static_cast<s32>(m_window_info.surface_width); }
|
||||
ALWAYS_INLINE s32 GetWindowHeight() const { return static_cast<s32>(m_window_info.surface_height); }
|
||||
ALWAYS_INLINE float GetWindowScale() const { return m_window_info.surface_scale; }
|
||||
|
||||
// Position is relative to the top-left corner of the window.
|
||||
ALWAYS_INLINE s32 GetMousePositionX() const { return m_mouse_position_x; }
|
||||
ALWAYS_INLINE s32 GetMousePositionY() const { return m_mouse_position_y; }
|
||||
ALWAYS_INLINE void SetMousePosition(s32 x, s32 y)
|
||||
{
|
||||
m_mouse_position_x = x;
|
||||
m_mouse_position_y = y;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE const void* GetDisplayTextureHandle() const { return m_display_texture; }
|
||||
ALWAYS_INLINE s32 GetDisplayWidth() const { return m_display_width; }
|
||||
ALWAYS_INLINE s32 GetDisplayHeight() const { return m_display_height; }
|
||||
ALWAYS_INLINE float GetDisplayAspectRatio() const { return m_display_aspect_ratio; }
|
||||
ALWAYS_INLINE bool IsGPUTimingEnabled() const { return m_gpu_timing_enabled; }
|
||||
|
||||
virtual RenderAPI GetRenderAPI() const = 0;
|
||||
virtual void* GetDevice() const = 0;
|
||||
virtual void* GetContext() const = 0;
|
||||
|
||||
virtual bool HasDevice() const = 0;
|
||||
virtual bool HasSurface() const = 0;
|
||||
|
||||
virtual bool CreateDevice(const WindowInfo& wi, bool vsync) = 0;
|
||||
virtual bool SetupDevice() = 0;
|
||||
virtual bool MakeCurrent() = 0;
|
||||
virtual bool DoneCurrent() = 0;
|
||||
virtual void DestroySurface() = 0;
|
||||
virtual bool ChangeWindow(const WindowInfo& wi) = 0;
|
||||
virtual bool SupportsFullscreen() const = 0;
|
||||
virtual bool IsFullscreen() = 0;
|
||||
virtual bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) = 0;
|
||||
virtual AdapterAndModeList GetAdapterAndModeList() = 0;
|
||||
virtual bool CreateResources() = 0;
|
||||
virtual void DestroyResources();
|
||||
|
||||
virtual bool SetPostProcessingChain(const std::string_view& config) = 0;
|
||||
|
||||
/// Call when the window size changes externally to recreate any resources.
|
||||
virtual void ResizeWindow(s32 new_window_width, s32 new_window_height) = 0;
|
||||
|
||||
/// Creates an abstracted RGBA8 texture. If dynamic, the texture can be updated with UpdateTexture() below.
|
||||
virtual std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
|
||||
GPUTexture::Format format, const void* data, u32 data_stride,
|
||||
bool dynamic = false) = 0;
|
||||
virtual bool BeginTextureUpdate(GPUTexture* texture, u32 width, u32 height, void** out_buffer, u32* out_pitch) = 0;
|
||||
virtual void EndTextureUpdate(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height) = 0;
|
||||
|
||||
virtual bool UpdateTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 pitch);
|
||||
|
||||
virtual bool DownloadTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, void* out_data,
|
||||
u32 out_data_stride) = 0;
|
||||
|
||||
/// Returns false if the window was completely occluded.
|
||||
virtual bool Render(bool skip_present) = 0;
|
||||
|
||||
/// Renders the display with postprocessing to the specified image.
|
||||
virtual bool RenderScreenshot(u32 width, u32 height, const Common::Rectangle<s32>& draw_rect,
|
||||
std::vector<u32>* out_pixels, u32* out_stride, GPUTexture::Format* out_format) = 0;
|
||||
|
||||
ALWAYS_INLINE bool IsVsyncEnabled() const { return m_vsync_enabled; }
|
||||
virtual void SetVSync(bool enabled) = 0;
|
||||
|
||||
/// ImGui context management, usually called by derived classes.
|
||||
virtual bool CreateImGuiContext() = 0;
|
||||
virtual void DestroyImGuiContext() = 0;
|
||||
virtual bool UpdateImGuiFontTexture() = 0;
|
||||
|
||||
bool UsesLowerLeftOrigin() const;
|
||||
void SetDisplayMaxFPS(float max_fps);
|
||||
bool ShouldSkipDisplayingFrame();
|
||||
void ThrottlePresentation();
|
||||
|
||||
void ClearDisplayTexture()
|
||||
{
|
||||
m_display_texture = nullptr;
|
||||
m_display_texture_view_x = 0;
|
||||
m_display_texture_view_y = 0;
|
||||
m_display_texture_view_width = 0;
|
||||
m_display_texture_view_height = 0;
|
||||
m_display_changed = true;
|
||||
}
|
||||
|
||||
void SetDisplayTexture(GPUTexture* texture, s32 view_x, s32 view_y, s32 view_width, s32 view_height)
|
||||
{
|
||||
m_display_texture = texture;
|
||||
m_display_texture_view_x = view_x;
|
||||
m_display_texture_view_y = view_y;
|
||||
m_display_texture_view_width = view_width;
|
||||
m_display_texture_view_height = view_height;
|
||||
m_display_changed = true;
|
||||
}
|
||||
|
||||
void SetDisplayTextureRect(s32 view_x, s32 view_y, s32 view_width, s32 view_height)
|
||||
{
|
||||
m_display_texture_view_x = view_x;
|
||||
m_display_texture_view_y = view_y;
|
||||
m_display_texture_view_width = view_width;
|
||||
m_display_texture_view_height = view_height;
|
||||
m_display_changed = true;
|
||||
}
|
||||
|
||||
void SetDisplayParameters(s32 display_width, s32 display_height, s32 active_left, s32 active_top, s32 active_width,
|
||||
s32 active_height, float display_aspect_ratio)
|
||||
{
|
||||
m_display_width = display_width;
|
||||
m_display_height = display_height;
|
||||
m_display_active_left = active_left;
|
||||
m_display_active_top = active_top;
|
||||
m_display_active_width = active_width;
|
||||
m_display_active_height = active_height;
|
||||
m_display_aspect_ratio = display_aspect_ratio;
|
||||
m_display_changed = true;
|
||||
}
|
||||
|
||||
virtual bool SupportsTextureFormat(GPUTexture::Format format) const = 0;
|
||||
|
||||
virtual bool GetHostRefreshRate(float* refresh_rate);
|
||||
|
||||
/// Enables/disables GPU frame timing.
|
||||
virtual bool SetGPUTimingEnabled(bool enabled);
|
||||
|
||||
/// Returns the amount of GPU time utilized since the last time this method was called.
|
||||
virtual float GetAndResetAccumulatedGPUTime();
|
||||
|
||||
/// Sets the software cursor to the specified texture. Ownership of the texture is transferred.
|
||||
void SetSoftwareCursor(std::unique_ptr<GPUTexture> texture, float scale = 1.0f);
|
||||
|
||||
/// Sets the software cursor to the specified image.
|
||||
bool SetSoftwareCursor(const void* pixels, u32 width, u32 height, u32 stride, float scale = 1.0f);
|
||||
|
||||
/// Sets the software cursor to the specified path (png image).
|
||||
bool SetSoftwareCursor(const char* path, float scale = 1.0f);
|
||||
|
||||
/// Disables the software cursor.
|
||||
void ClearSoftwareCursor();
|
||||
|
||||
/// Helper function for computing the draw rectangle in a larger window.
|
||||
std::tuple<s32, s32, s32, s32> CalculateDrawRect(s32 window_width, s32 window_height,
|
||||
bool apply_aspect_ratio = true) const;
|
||||
|
||||
/// Helper function for converting window coordinates to display coordinates.
|
||||
std::tuple<float, float> ConvertWindowCoordinatesToDisplayCoordinates(s32 window_x, s32 window_y, s32 window_width,
|
||||
s32 window_height) const;
|
||||
|
||||
/// Helper function to save texture data to a PNG. If flip_y is set, the image will be flipped aka OpenGL.
|
||||
bool WriteTextureToFile(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, std::string filename,
|
||||
bool clear_alpha = true, bool flip_y = false, u32 resize_width = 0, u32 resize_height = 0,
|
||||
bool compress_on_thread = false);
|
||||
|
||||
/// Helper function to save current display texture to PNG.
|
||||
bool WriteDisplayTextureToFile(std::string filename, bool full_resolution = true, bool apply_aspect_ratio = true,
|
||||
bool compress_on_thread = false);
|
||||
|
||||
/// Helper function to save current display texture to a buffer.
|
||||
bool WriteDisplayTextureToBuffer(std::vector<u32>* buffer, u32 resize_width = 0, u32 resize_height = 0,
|
||||
bool clear_alpha = true);
|
||||
|
||||
/// Helper function to save screenshot to PNG.
|
||||
bool WriteScreenshotToFile(std::string filename, bool internal_resolution = false, bool compress_on_thread = false);
|
||||
|
||||
protected:
|
||||
ALWAYS_INLINE bool HasSoftwareCursor() const { return static_cast<bool>(m_cursor_texture); }
|
||||
ALWAYS_INLINE bool HasDisplayTexture() const { return (m_display_texture != nullptr); }
|
||||
|
||||
bool IsUsingLinearFiltering() const;
|
||||
|
||||
void CalculateDrawRect(s32 window_width, s32 window_height, float* out_left, float* out_top, float* out_width,
|
||||
float* out_height, float* out_left_padding, float* out_top_padding, float* out_scale,
|
||||
float* out_x_scale, bool apply_aspect_ratio = true) const;
|
||||
|
||||
std::tuple<s32, s32, s32, s32> CalculateSoftwareCursorDrawRect() const;
|
||||
std::tuple<s32, s32, s32, s32> CalculateSoftwareCursorDrawRect(s32 cursor_x, s32 cursor_y) const;
|
||||
|
||||
WindowInfo m_window_info;
|
||||
|
||||
u64 m_last_frame_displayed_time = 0;
|
||||
|
||||
s32 m_mouse_position_x = 0;
|
||||
s32 m_mouse_position_y = 0;
|
||||
|
||||
s32 m_display_width = 0;
|
||||
s32 m_display_height = 0;
|
||||
s32 m_display_active_left = 0;
|
||||
s32 m_display_active_top = 0;
|
||||
s32 m_display_active_width = 0;
|
||||
s32 m_display_active_height = 0;
|
||||
float m_display_aspect_ratio = 1.0f;
|
||||
float m_display_frame_interval = 0.0f;
|
||||
|
||||
GPUTexture* m_display_texture = nullptr;
|
||||
s32 m_display_texture_view_x = 0;
|
||||
s32 m_display_texture_view_y = 0;
|
||||
s32 m_display_texture_view_width = 0;
|
||||
s32 m_display_texture_view_height = 0;
|
||||
|
||||
std::unique_ptr<GPUTexture> m_cursor_texture;
|
||||
float m_cursor_texture_scale = 1.0f;
|
||||
|
||||
bool m_display_changed = false;
|
||||
bool m_gpu_timing_enabled = false;
|
||||
bool m_vsync_enabled = false;
|
||||
};
|
||||
|
||||
/// Returns a pointer to the current host display abstraction. Assumes AcquireHostDisplay() has been called.
|
||||
extern std::unique_ptr<HostDisplay> g_host_display;
|
||||
|
||||
namespace Host {
|
||||
std::unique_ptr<HostDisplay> CreateDisplayForAPI(RenderAPI api);
|
||||
|
||||
/// Creates the host display. This may create a new window. The API used depends on the current configuration.
|
||||
bool AcquireHostDisplay(RenderAPI api);
|
||||
|
||||
/// Destroys the host display. This may close the display window.
|
||||
void ReleaseHostDisplay();
|
||||
|
||||
/// Returns false if the window was completely occluded. If frame_skip is set, the frame won't be
|
||||
/// displayed, but the GPU command queue will still be flushed.
|
||||
// bool BeginPresentFrame(bool frame_skip);
|
||||
|
||||
/// Presents the frame to the display, and renders OSD elements.
|
||||
// void EndPresentFrame();
|
||||
|
||||
/// Provided by the host; renders the display.
|
||||
void RenderDisplay(bool skip_present);
|
||||
void InvalidateDisplay();
|
||||
} // namespace Host
|
||||
@@ -0,0 +1,192 @@
|
||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#include "core/host.h"
|
||||
#include "core/host_settings.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/layered_settings_interface.h"
|
||||
|
||||
static std::mutex s_settings_mutex;
|
||||
static LayeredSettingsInterface s_layered_settings_interface;
|
||||
|
||||
std::unique_lock<std::mutex> Host::GetSettingsLock()
|
||||
{
|
||||
return std::unique_lock<std::mutex>(s_settings_mutex);
|
||||
}
|
||||
|
||||
SettingsInterface* Host::GetSettingsInterface()
|
||||
{
|
||||
return &s_layered_settings_interface;
|
||||
}
|
||||
|
||||
SettingsInterface* Host::GetSettingsInterfaceForBindings()
|
||||
{
|
||||
SettingsInterface* input_layer = s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_INPUT);
|
||||
return input_layer ? input_layer : &s_layered_settings_interface;
|
||||
}
|
||||
|
||||
std::string Host::GetBaseStringSettingValue(const char* section, const char* key, const char* default_value /*= ""*/)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
return s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE)->GetStringValue(section, key, default_value);
|
||||
}
|
||||
|
||||
bool Host::GetBaseBoolSettingValue(const char* section, const char* key, bool default_value /*= false*/)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
return s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE)->GetBoolValue(section, key, default_value);
|
||||
}
|
||||
|
||||
s32 Host::GetBaseIntSettingValue(const char* section, const char* key, s32 default_value /*= 0*/)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
return s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE)->GetIntValue(section, key, default_value);
|
||||
}
|
||||
|
||||
u32 Host::GetBaseUIntSettingValue(const char* section, const char* key, u32 default_value /*= 0*/)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
return s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE)->GetUIntValue(section, key, default_value);
|
||||
}
|
||||
|
||||
float Host::GetBaseFloatSettingValue(const char* section, const char* key, float default_value /*= 0.0f*/)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
return s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE)->GetFloatValue(section, key, default_value);
|
||||
}
|
||||
|
||||
double Host::GetBaseDoubleSettingValue(const char* section, const char* key, double default_value /* = 0.0f */)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
return s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE)->GetDoubleValue(section, key, default_value);
|
||||
}
|
||||
|
||||
std::vector<std::string> Host::GetBaseStringListSetting(const char* section, const char* key)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
return s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE)->GetStringList(section, key);
|
||||
}
|
||||
|
||||
std::string Host::GetStringSettingValue(const char* section, const char* key, const char* default_value /*= ""*/)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
return s_layered_settings_interface.GetStringValue(section, key, default_value);
|
||||
}
|
||||
|
||||
bool Host::GetBoolSettingValue(const char* section, const char* key, bool default_value /*= false*/)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
return s_layered_settings_interface.GetBoolValue(section, key, default_value);
|
||||
}
|
||||
|
||||
s32 Host::GetIntSettingValue(const char* section, const char* key, s32 default_value /*= 0*/)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
return s_layered_settings_interface.GetIntValue(section, key, default_value);
|
||||
}
|
||||
|
||||
u32 Host::GetUIntSettingValue(const char* section, const char* key, u32 default_value /*= 0*/)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
return s_layered_settings_interface.GetUIntValue(section, key, default_value);
|
||||
}
|
||||
|
||||
float Host::GetFloatSettingValue(const char* section, const char* key, float default_value /*= 0.0f*/)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
return s_layered_settings_interface.GetFloatValue(section, key, default_value);
|
||||
}
|
||||
|
||||
double Host::GetDoubleSettingValue(const char* section, const char* key, double default_value /*= 0.0f*/)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
return s_layered_settings_interface.GetDoubleValue(section, key, default_value);
|
||||
}
|
||||
|
||||
std::vector<std::string> Host::GetStringListSetting(const char* section, const char* key)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
return s_layered_settings_interface.GetStringList(section, key);
|
||||
}
|
||||
|
||||
void Host::SetBaseBoolSettingValue(const char* section, const char* key, bool value)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE)->SetBoolValue(section, key, value);
|
||||
}
|
||||
|
||||
void Host::SetBaseIntSettingValue(const char* section, const char* key, int value)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE)->SetIntValue(section, key, value);
|
||||
}
|
||||
|
||||
void Host::SetBaseFloatSettingValue(const char* section, const char* key, float value)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE)->SetFloatValue(section, key, value);
|
||||
}
|
||||
|
||||
void Host::SetBaseStringSettingValue(const char* section, const char* key, const char* value)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE)->SetStringValue(section, key, value);
|
||||
}
|
||||
|
||||
void Host::SetBaseStringListSettingValue(const char* section, const char* key, const std::vector<std::string>& values)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE)->SetStringList(section, key, values);
|
||||
}
|
||||
|
||||
bool Host::AddValueToBaseStringListSetting(const char* section, const char* key, const char* value)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
return s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE)->AddToStringList(section, key, value);
|
||||
}
|
||||
|
||||
bool Host::RemoveValueFromBaseStringListSetting(const char* section, const char* key, const char* value)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
return s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE)
|
||||
->RemoveFromStringList(section, key, value);
|
||||
}
|
||||
|
||||
void Host::DeleteBaseSettingValue(const char* section, const char* key)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE)->DeleteValue(section, key);
|
||||
}
|
||||
|
||||
SettingsInterface* Host::Internal::GetBaseSettingsLayer()
|
||||
{
|
||||
return s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE);
|
||||
}
|
||||
|
||||
SettingsInterface* Host::Internal::GetGameSettingsLayer()
|
||||
{
|
||||
return s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_GAME);
|
||||
}
|
||||
|
||||
SettingsInterface* Host::Internal::GetInputSettingsLayer()
|
||||
{
|
||||
return s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_INPUT);
|
||||
}
|
||||
|
||||
void Host::Internal::SetBaseSettingsLayer(SettingsInterface* sif)
|
||||
{
|
||||
AssertMsg(s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE) == nullptr, "Base layer has not been set");
|
||||
s_layered_settings_interface.SetLayer(LayeredSettingsInterface::LAYER_BASE, sif);
|
||||
}
|
||||
|
||||
void Host::Internal::SetGameSettingsLayer(SettingsInterface* sif)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
s_layered_settings_interface.SetLayer(LayeredSettingsInterface::LAYER_GAME, sif);
|
||||
}
|
||||
|
||||
void Host::Internal::SetInputSettingsLayer(SettingsInterface* sif)
|
||||
{
|
||||
std::unique_lock lock(s_settings_mutex);
|
||||
s_layered_settings_interface.SetLayer(LayeredSettingsInterface::LAYER_INPUT, sif);
|
||||
}
|
||||
@@ -0,0 +1,885 @@
|
||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#include "imgui_overlays.h"
|
||||
#include "controller.h"
|
||||
#include "fullscreen_ui.h"
|
||||
#include "gpu.h"
|
||||
#include "host.h"
|
||||
#include "host_settings.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
#include "spu.h"
|
||||
#include "system.h"
|
||||
|
||||
#include "util/audio_stream.h"
|
||||
#include "util/host_display.h"
|
||||
#include "util/imgui_fullscreen.h"
|
||||
#include "util/imgui_manager.h"
|
||||
#include "util/input_manager.h"
|
||||
|
||||
#include "common/align.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/file_system.h"
|
||||
#include "common/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/timer.h"
|
||||
|
||||
#include "IconsFontAwesome5.h"
|
||||
#include "fmt/chrono.h"
|
||||
#include "fmt/format.h"
|
||||
#include "gsl/span"
|
||||
#include "imgui.h"
|
||||
#include "imgui_internal.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#if defined(CPU_X64)
|
||||
#include <emmintrin.h>
|
||||
#elif defined(CPU_AARCH64)
|
||||
#ifdef _MSC_VER
|
||||
#include <arm64_neon.h>
|
||||
#else
|
||||
#include <arm_neon.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
Log_SetChannel(ImGuiManager);
|
||||
|
||||
namespace ImGuiManager {
|
||||
static void FormatProcessorStat(String& text, double usage, double time);
|
||||
static void DrawPerformanceOverlay();
|
||||
static void DrawEnhancementsOverlay();
|
||||
static void DrawInputsOverlay();
|
||||
} // namespace ImGuiManager
|
||||
|
||||
namespace SaveStateSelectorUI {
|
||||
static void Draw();
|
||||
}
|
||||
|
||||
static std::tuple<float, float> GetMinMax(gsl::span<const float> values)
|
||||
{
|
||||
#if defined(CPU_X64)
|
||||
__m128 vmin(_mm_loadu_ps(values.data()));
|
||||
__m128 vmax(vmin);
|
||||
|
||||
const u32 count = static_cast<u32>(values.size());
|
||||
const u32 aligned_count = Common::AlignDownPow2(count, 4);
|
||||
u32 i = 4;
|
||||
for (; i < aligned_count; i += 4)
|
||||
{
|
||||
const __m128 v(_mm_loadu_ps(&values[i]));
|
||||
vmin = _mm_min_ps(v);
|
||||
vmax = _mm_max_ps(v);
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
float min = std::min(vmin.m128_f32[0], std::min(vmin.m128_f32[1], std::min(vmin.m128_f32[2], vmin.m128_f32[3])));
|
||||
float max = std::max(vmax.m128_f32[0], std::max(vmax.m128_f32[1], std::max(vmax.m128_f32[2], vmax.m128_f32[3])));
|
||||
#else
|
||||
float min = std::min(vmin[0], std::min(vmin[1], std::min(vmin[2], vmin[3])));
|
||||
float max = std::max(vmax[0], std::max(vmax[1], std::max(vmax[2], vmax[3])));
|
||||
#endif
|
||||
for (; i < count; i++)
|
||||
{
|
||||
min = std::min(min, values[i]);
|
||||
max = std::max(max, values[i]);
|
||||
}
|
||||
|
||||
return std::tie(min, max);
|
||||
#elif defined(CPU_AARCH64)
|
||||
float32x4_t vmin(vld1q_f32(values.data()));
|
||||
float32x4_t vmax(vmin);
|
||||
|
||||
const u32 count = static_cast<u32>(values.size());
|
||||
const u32 aligned_count = Common::AlignDownPow2(count, 4);
|
||||
u32 i = 4;
|
||||
for (; i < aligned_count; i += 4)
|
||||
{
|
||||
const float32x4_t v(vld1q_f32(&values[i]));
|
||||
vmin = vminq_f32(v);
|
||||
vmax = vmaxq_f32(v);
|
||||
}
|
||||
|
||||
float min = vminvq_f32(vmin);
|
||||
float max = vmaxvq_f32(vmax);
|
||||
for (; i < count; i++)
|
||||
{
|
||||
min = std::min(min, values[i]);
|
||||
max = std::max(max, values[i]);
|
||||
}
|
||||
|
||||
return std::tie(min, max);
|
||||
#else
|
||||
float min = values[0];
|
||||
float max = values[0];
|
||||
const u32 count = static_cast<u32>(values.size());
|
||||
for (u32 i = 1; i < count; i++)
|
||||
{
|
||||
min = std::min(min, values[i]);
|
||||
max = std::max(max, values[i]);
|
||||
}
|
||||
|
||||
return std::tie(min, max);
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool s_save_state_selector_ui_open = false;
|
||||
|
||||
void ImGuiManager::RenderTextOverlays()
|
||||
{
|
||||
const System::State state = System::GetState();
|
||||
if (state != System::State::Shutdown)
|
||||
{
|
||||
DrawPerformanceOverlay();
|
||||
|
||||
if (g_settings.display_show_enhancements && state != System::State::Paused)
|
||||
DrawEnhancementsOverlay();
|
||||
|
||||
if (g_settings.display_show_inputs && state != System::State::Paused)
|
||||
DrawInputsOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiManager::RenderOverlayWindows()
|
||||
{
|
||||
const System::State state = System::GetState();
|
||||
if (state != System::State::Shutdown)
|
||||
{
|
||||
if (s_save_state_selector_ui_open)
|
||||
SaveStateSelectorUI::Draw();
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiManager::FormatProcessorStat(String& text, double usage, double time)
|
||||
{
|
||||
// Some values, such as GPU (and even CPU to some extent) can be out of phase with the wall clock,
|
||||
// which the processor time is divided by to get a utilization percentage. Let's clamp it at 100%,
|
||||
// so that people don't get confused, and remove the decimal places when it's there while we're at it.
|
||||
if (usage >= 99.95)
|
||||
text.AppendFmtString("100% ({:.2f}ms)", time);
|
||||
else
|
||||
text.AppendFmtString("{:.1f}% ({:.2f}ms)", usage, time);
|
||||
}
|
||||
|
||||
void ImGuiManager::DrawPerformanceOverlay()
|
||||
{
|
||||
if (!(g_settings.display_show_fps || g_settings.display_show_speed || g_settings.display_show_resolution ||
|
||||
g_settings.display_show_cpu ||
|
||||
(g_settings.display_show_status_indicators &&
|
||||
(System::IsPaused() || System::IsFastForwardEnabled() || System::IsTurboEnabled()))))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const float scale = ImGuiManager::GetGlobalScale();
|
||||
const float shadow_offset = std::ceil(1.0f * scale);
|
||||
const float margin = std::ceil(10.0f * scale);
|
||||
const float spacing = std::ceil(5.0f * scale);
|
||||
ImFont* fixed_font = ImGuiManager::GetFixedFont();
|
||||
ImFont* standard_font = ImGuiManager::GetStandardFont();
|
||||
float position_y = margin;
|
||||
|
||||
ImDrawList* dl = ImGui::GetBackgroundDrawList();
|
||||
SmallString text;
|
||||
ImVec2 text_size;
|
||||
bool first = true;
|
||||
|
||||
#define DRAW_LINE(font, text, color) \
|
||||
do \
|
||||
{ \
|
||||
text_size = \
|
||||
font->CalcTextSizeA(font->FontSize, std::numeric_limits<float>::max(), -1.0f, (text), nullptr, nullptr); \
|
||||
dl->AddText( \
|
||||
font, font->FontSize, \
|
||||
ImVec2(ImGui::GetIO().DisplaySize.x - margin - text_size.x + shadow_offset, position_y + shadow_offset), \
|
||||
IM_COL32(0, 0, 0, 100), text, text.GetCharArray() + text.GetLength()); \
|
||||
dl->AddText(font, font->FontSize, ImVec2(ImGui::GetIO().DisplaySize.x - margin - text_size.x, position_y), color, \
|
||||
(text)); \
|
||||
position_y += text_size.y + spacing; \
|
||||
} while (0)
|
||||
|
||||
const System::State state = System::GetState();
|
||||
if (state == System::State::Running)
|
||||
{
|
||||
const float speed = System::GetEmulationSpeed();
|
||||
if (g_settings.display_show_fps)
|
||||
{
|
||||
text.AppendFmtString("G: {:.2f} | V: {:.2f}", System::GetFPS(), System::GetVPS());
|
||||
first = false;
|
||||
}
|
||||
if (g_settings.display_show_speed)
|
||||
{
|
||||
text.AppendFmtString("{}{}%", first ? "" : " | ", static_cast<u32>(std::round(speed)));
|
||||
|
||||
const float target_speed = System::GetTargetSpeed();
|
||||
if (target_speed <= 0.0f)
|
||||
text.AppendString(" (Max)");
|
||||
else
|
||||
text.AppendFmtString(" ({:.0f}%)", target_speed * 100.0f);
|
||||
|
||||
first = false;
|
||||
}
|
||||
if (!text.IsEmpty())
|
||||
{
|
||||
ImU32 color;
|
||||
if (speed < 95.0f)
|
||||
color = IM_COL32(255, 100, 100, 255);
|
||||
else if (speed > 105.0f)
|
||||
color = IM_COL32(100, 255, 100, 255);
|
||||
else
|
||||
color = IM_COL32(255, 255, 255, 255);
|
||||
|
||||
DRAW_LINE(fixed_font, text, color);
|
||||
}
|
||||
|
||||
if (g_settings.display_show_resolution)
|
||||
{
|
||||
const auto [effective_width, effective_height] = g_gpu->GetEffectiveDisplayResolution();
|
||||
const bool interlaced = g_gpu->IsInterlacedDisplayEnabled();
|
||||
const bool pal = g_gpu->IsInPALMode();
|
||||
text.Fmt("{}x{} {} {}", effective_width, effective_height, pal ? "PAL" : "NTSC",
|
||||
interlaced ? "Interlaced" : "Progressive");
|
||||
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
|
||||
}
|
||||
|
||||
if (g_settings.display_show_cpu)
|
||||
{
|
||||
text.Clear();
|
||||
text.AppendFmtString("{:.2f}ms | {:.2f}ms | {:.2f}ms", System::GetMinimumFrameTime(),
|
||||
System::GetAverageFrameTime(), System::GetMaximumFrameTime());
|
||||
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
|
||||
|
||||
text.Clear();
|
||||
if (g_settings.cpu_overclock_active || (!g_settings.IsUsingRecompiler() || g_settings.cpu_recompiler_icache ||
|
||||
g_settings.cpu_recompiler_memory_exceptions))
|
||||
{
|
||||
first = true;
|
||||
text.AppendString("CPU[");
|
||||
if (g_settings.cpu_overclock_active)
|
||||
{
|
||||
text.AppendFmtString("{}", g_settings.GetCPUOverclockPercent());
|
||||
first = false;
|
||||
}
|
||||
if (g_settings.cpu_execution_mode == CPUExecutionMode::Interpreter)
|
||||
{
|
||||
text.AppendFmtString("{}{}", first ? "" : "/", "I");
|
||||
first = false;
|
||||
}
|
||||
else if (g_settings.cpu_execution_mode == CPUExecutionMode::CachedInterpreter)
|
||||
{
|
||||
text.AppendFmtString("{}{}", first ? "" : "/", "CI");
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (g_settings.cpu_recompiler_icache)
|
||||
{
|
||||
text.AppendFmtString("{}{}", first ? "" : "/", "IC");
|
||||
first = false;
|
||||
}
|
||||
if (g_settings.cpu_recompiler_memory_exceptions)
|
||||
{
|
||||
text.AppendFmtString("{}{}", first ? "" : "/", "ME");
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
text.AppendString("]: ");
|
||||
}
|
||||
else
|
||||
{
|
||||
text.Assign("CPU: ");
|
||||
}
|
||||
FormatProcessorStat(text, System::GetCPUThreadUsage(), System::GetCPUThreadAverageTime());
|
||||
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
|
||||
|
||||
if (g_gpu->GetSWThread())
|
||||
{
|
||||
text.Assign("SW: ");
|
||||
FormatProcessorStat(text, System::GetSWThreadUsage(), System::GetSWThreadAverageTime());
|
||||
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
|
||||
}
|
||||
|
||||
#if 0
|
||||
{
|
||||
AudioStream* stream = g_spu.GetOutputStream();
|
||||
const u32 frames = stream->GetBufferedFramesRelaxed();
|
||||
text.Clear();
|
||||
text.Fmt("Audio: {:<4u}f/{:<3u}ms", frames, AudioStream::GetMSForBufferSize(stream->GetSampleRate(), frames));
|
||||
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (g_settings.display_show_gpu && g_host_display->IsGPUTimingEnabled())
|
||||
{
|
||||
text.Assign("GPU: ");
|
||||
FormatProcessorStat(text, System::GetGPUUsage(), System::GetGPUAverageTime());
|
||||
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
|
||||
}
|
||||
|
||||
if (g_settings.display_show_status_indicators)
|
||||
{
|
||||
const bool rewinding = System::IsRewinding();
|
||||
if (rewinding || System::IsFastForwardEnabled() || System::IsTurboEnabled())
|
||||
{
|
||||
text.Assign(rewinding ? ICON_FA_FAST_BACKWARD : ICON_FA_FAST_FORWARD);
|
||||
DRAW_LINE(standard_font, text, IM_COL32(255, 255, 255, 255));
|
||||
}
|
||||
}
|
||||
|
||||
if (g_settings.display_show_frame_times)
|
||||
{
|
||||
const ImVec2 history_size(200.0f * scale, 50.0f * scale);
|
||||
ImGui::SetNextWindowSize(ImVec2(history_size.x, history_size.y));
|
||||
ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x - margin - history_size.x, position_y));
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.25f));
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_PlotLines, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
|
||||
ImGui::PushFont(fixed_font);
|
||||
if (ImGui::Begin("##frame_times", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs))
|
||||
{
|
||||
auto [min, max] = GetMinMax(System::GetFrameTimeHistory());
|
||||
|
||||
// add a little bit of space either side, so we're not constantly resizing
|
||||
if ((max - min) < 4.0f)
|
||||
{
|
||||
min = min - std::fmod(min, 1.0f);
|
||||
max = max - std::fmod(max, 1.0f) + 1.0f;
|
||||
min = std::max(min - 2.0f, 0.0f);
|
||||
max += 2.0f;
|
||||
}
|
||||
|
||||
ImGui::PlotEx(
|
||||
ImGuiPlotType_Lines, "##frame_times",
|
||||
[](void*, int idx) -> float {
|
||||
return System::GetFrameTimeHistory()[((System::GetFrameTimeHistoryPos() + idx) %
|
||||
System::NUM_FRAME_TIME_SAMPLES)];
|
||||
},
|
||||
nullptr, System::NUM_FRAME_TIME_SAMPLES, 0, nullptr, min, max, history_size);
|
||||
|
||||
ImDrawList* win_dl = ImGui::GetCurrentWindow()->DrawList;
|
||||
const ImVec2 wpos(ImGui::GetCurrentWindow()->Pos);
|
||||
|
||||
text.Clear();
|
||||
text.AppendFmtString("{:.1f} ms", max);
|
||||
text_size = fixed_font->CalcTextSizeA(fixed_font->FontSize, FLT_MAX, 0.0f, text.GetCharArray(),
|
||||
text.GetCharArray() + text.GetLength());
|
||||
win_dl->AddText(ImVec2(wpos.x + history_size.x - text_size.x - spacing + shadow_offset, wpos.y + shadow_offset),
|
||||
IM_COL32(0, 0, 0, 100), text.GetCharArray(), text.GetCharArray() + text.GetLength());
|
||||
win_dl->AddText(ImVec2(wpos.x + history_size.x - text_size.x - spacing, wpos.y), IM_COL32(255, 255, 255, 255),
|
||||
text.GetCharArray(), text.GetCharArray() + text.GetLength());
|
||||
|
||||
text.Clear();
|
||||
text.AppendFmtString("{:.1f} ms", min);
|
||||
text_size = fixed_font->CalcTextSizeA(fixed_font->FontSize, FLT_MAX, 0.0f, text.GetCharArray(),
|
||||
text.GetCharArray() + text.GetLength());
|
||||
win_dl->AddText(ImVec2(wpos.x + history_size.x - text_size.x - spacing + shadow_offset,
|
||||
wpos.y + history_size.y - fixed_font->FontSize + shadow_offset),
|
||||
IM_COL32(0, 0, 0, 100), text.GetCharArray(), text.GetCharArray() + text.GetLength());
|
||||
win_dl->AddText(
|
||||
ImVec2(wpos.x + history_size.x - text_size.x - spacing, wpos.y + history_size.y - fixed_font->FontSize),
|
||||
IM_COL32(255, 255, 255, 255), text.GetCharArray(), text.GetCharArray() + text.GetLength());
|
||||
}
|
||||
ImGui::End();
|
||||
ImGui::PopFont();
|
||||
ImGui::PopStyleVar(5);
|
||||
ImGui::PopStyleColor(3);
|
||||
}
|
||||
}
|
||||
else if (g_settings.display_show_status_indicators && state == System::State::Paused &&
|
||||
!FullscreenUI::HasActiveWindow())
|
||||
{
|
||||
text.Assign(ICON_FA_PAUSE);
|
||||
DRAW_LINE(standard_font, text, IM_COL32(255, 255, 255, 255));
|
||||
}
|
||||
|
||||
#undef DRAW_LINE
|
||||
}
|
||||
|
||||
void ImGuiManager::DrawEnhancementsOverlay()
|
||||
{
|
||||
LargeString text;
|
||||
text.AppendFmtString("{} {}", Settings::GetConsoleRegionName(System::GetRegion()),
|
||||
Settings::GetRendererName(g_gpu->GetRendererType()));
|
||||
|
||||
if (g_settings.rewind_enable)
|
||||
text.AppendFormattedString(" RW=%g/%u", g_settings.rewind_save_frequency, g_settings.rewind_save_slots);
|
||||
if (g_settings.IsRunaheadEnabled())
|
||||
text.AppendFormattedString(" RA=%u", g_settings.runahead_frames);
|
||||
|
||||
if (g_settings.cpu_overclock_active)
|
||||
text.AppendFormattedString(" CPU=%u%%", g_settings.GetCPUOverclockPercent());
|
||||
if (g_settings.enable_8mb_ram)
|
||||
text.AppendString(" 8MB");
|
||||
if (g_settings.cdrom_read_speedup != 1)
|
||||
text.AppendFormattedString(" CDR=%ux", g_settings.cdrom_read_speedup);
|
||||
if (g_settings.cdrom_seek_speedup != 1)
|
||||
text.AppendFormattedString(" CDS=%ux", g_settings.cdrom_seek_speedup);
|
||||
if (g_settings.gpu_resolution_scale != 1)
|
||||
text.AppendFormattedString(" IR=%ux", g_settings.gpu_resolution_scale);
|
||||
if (g_settings.gpu_multisamples != 1)
|
||||
{
|
||||
text.AppendFormattedString(" %ux%s", g_settings.gpu_multisamples,
|
||||
g_settings.gpu_per_sample_shading ? "SSAA" : "MSAA");
|
||||
}
|
||||
if (g_settings.gpu_true_color)
|
||||
text.AppendString(" TrueCol");
|
||||
if (g_settings.gpu_disable_interlacing)
|
||||
text.AppendString(" ForceProg");
|
||||
if (g_settings.gpu_force_ntsc_timings && System::GetRegion() == ConsoleRegion::PAL)
|
||||
text.AppendString(" PAL60");
|
||||
if (g_settings.gpu_texture_filter != GPUTextureFilter::Nearest)
|
||||
text.AppendFormattedString(" %s", Settings::GetTextureFilterName(g_settings.gpu_texture_filter));
|
||||
if (g_settings.gpu_widescreen_hack && g_settings.display_aspect_ratio != DisplayAspectRatio::Auto &&
|
||||
g_settings.display_aspect_ratio != DisplayAspectRatio::R4_3)
|
||||
{
|
||||
text.AppendString(" WSHack");
|
||||
}
|
||||
if (g_settings.gpu_pgxp_enable)
|
||||
{
|
||||
text.AppendString(" PGXP");
|
||||
if (g_settings.gpu_pgxp_culling)
|
||||
text.AppendString("/Cull");
|
||||
if (g_settings.gpu_pgxp_texture_correction)
|
||||
text.AppendString("/Tex");
|
||||
if (g_settings.gpu_pgxp_color_correction)
|
||||
text.AppendString("/Col");
|
||||
if (g_settings.gpu_pgxp_vertex_cache)
|
||||
text.AppendString("/VC");
|
||||
if (g_settings.gpu_pgxp_cpu)
|
||||
text.AppendString("/CPU");
|
||||
if (g_settings.gpu_pgxp_depth_buffer)
|
||||
text.AppendString("/Depth");
|
||||
}
|
||||
|
||||
const float scale = ImGuiManager::GetGlobalScale();
|
||||
const float shadow_offset = 1.0f * scale;
|
||||
const float margin = 10.0f * scale;
|
||||
ImFont* font = ImGuiManager::GetFixedFont();
|
||||
const float position_y = ImGui::GetIO().DisplaySize.y - margin - font->FontSize;
|
||||
|
||||
ImDrawList* dl = ImGui::GetBackgroundDrawList();
|
||||
ImVec2 text_size = font->CalcTextSizeA(font->FontSize, std::numeric_limits<float>::max(), -1.0f, text,
|
||||
text.GetCharArray() + text.GetLength(), nullptr);
|
||||
dl->AddText(font, font->FontSize,
|
||||
ImVec2(ImGui::GetIO().DisplaySize.x - margin - text_size.x + shadow_offset, position_y + shadow_offset),
|
||||
IM_COL32(0, 0, 0, 100), text, text.GetCharArray() + text.GetLength());
|
||||
dl->AddText(font, font->FontSize, ImVec2(ImGui::GetIO().DisplaySize.x - margin - text_size.x, position_y),
|
||||
IM_COL32(255, 255, 255, 255), text, text.GetCharArray() + text.GetLength());
|
||||
}
|
||||
|
||||
void ImGuiManager::DrawInputsOverlay()
|
||||
{
|
||||
const float scale = ImGuiManager::GetGlobalScale();
|
||||
const float shadow_offset = 1.0f * scale;
|
||||
const float margin = 10.0f * scale;
|
||||
const float spacing = 5.0f * scale;
|
||||
ImFont* font = ImGuiManager::GetFixedFont();
|
||||
|
||||
static constexpr u32 text_color = IM_COL32(0xff, 0xff, 0xff, 255);
|
||||
static constexpr u32 shadow_color = IM_COL32(0x00, 0x00, 0x00, 100);
|
||||
|
||||
const ImVec2& display_size = ImGui::GetIO().DisplaySize;
|
||||
ImDrawList* dl = ImGui::GetBackgroundDrawList();
|
||||
|
||||
u32 num_ports = 0;
|
||||
for (u32 port = 0; port < NUM_CONTROLLER_AND_CARD_PORTS; port++)
|
||||
{
|
||||
if (g_settings.controller_types[port] != ControllerType::None)
|
||||
num_ports++;
|
||||
}
|
||||
|
||||
float current_x = margin;
|
||||
float current_y = display_size.y - margin - ((static_cast<float>(num_ports) * (font->FontSize + spacing)) - spacing);
|
||||
|
||||
const ImVec4 clip_rect(current_x, current_y, display_size.x - margin, display_size.y - margin);
|
||||
|
||||
LargeString text;
|
||||
|
||||
for (u32 port = 0; port < NUM_CONTROLLER_AND_CARD_PORTS; port++)
|
||||
{
|
||||
if (g_settings.controller_types[port] == ControllerType::None)
|
||||
continue;
|
||||
|
||||
const Controller* controller = System::GetController(port);
|
||||
const Controller::ControllerInfo* cinfo =
|
||||
controller ? Controller::GetControllerInfo(controller->GetType()) : nullptr;
|
||||
if (!cinfo)
|
||||
continue;
|
||||
|
||||
text.Fmt("P{} |", port + 1u);
|
||||
|
||||
for (u32 bind = 0; bind < cinfo->num_bindings; bind++)
|
||||
{
|
||||
const Controller::ControllerBindingInfo& bi = cinfo->bindings[bind];
|
||||
switch (bi.type)
|
||||
{
|
||||
case InputBindingInfo::Type::Axis:
|
||||
case InputBindingInfo::Type::HalfAxis:
|
||||
{
|
||||
// axes are always shown
|
||||
const float value = controller->GetBindState(bi.bind_index);
|
||||
if (value >= (254.0f / 255.0f))
|
||||
text.AppendFmtString(" {}", bi.name);
|
||||
else if (value > (1.0f / 255.0f))
|
||||
text.AppendFmtString(" {}: {:.2f}", bi.name, value);
|
||||
}
|
||||
break;
|
||||
|
||||
case InputBindingInfo::Type::Button:
|
||||
{
|
||||
// buttons only shown when active
|
||||
const float value = controller->GetBindState(bi.bind_index);
|
||||
if (value >= 0.5f)
|
||||
text.AppendFmtString(" {}", bi.name);
|
||||
}
|
||||
break;
|
||||
|
||||
case InputBindingInfo::Type::Motor:
|
||||
case InputBindingInfo::Type::Macro:
|
||||
case InputBindingInfo::Type::Unknown:
|
||||
case InputBindingInfo::Type::Pointer:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dl->AddText(font, font->FontSize, ImVec2(current_x + shadow_offset, current_y + shadow_offset), shadow_color,
|
||||
text.GetCharArray(), text.GetCharArray() + text.GetLength(), 0.0f, &clip_rect);
|
||||
dl->AddText(font, font->FontSize, ImVec2(current_x, current_y), text_color, text.GetCharArray(),
|
||||
text.GetCharArray() + text.GetLength(), 0.0f, &clip_rect);
|
||||
|
||||
current_y += font->FontSize + spacing;
|
||||
}
|
||||
}
|
||||
|
||||
namespace SaveStateSelectorUI {
|
||||
struct ListEntry
|
||||
{
|
||||
std::string path;
|
||||
std::string serial;
|
||||
std::string title;
|
||||
std::string formatted_timestamp;
|
||||
std::unique_ptr<GPUTexture> preview_texture;
|
||||
s32 slot;
|
||||
bool global;
|
||||
};
|
||||
|
||||
static void InitializePlaceholderListEntry(ListEntry* li, std::string path, s32 slot, bool global);
|
||||
static void InitializeListEntry(ListEntry* li, ExtendedSaveStateInfo* ssi, std::string path, s32 slot, bool global);
|
||||
|
||||
static void RefreshHotkeyLegend();
|
||||
|
||||
static std::string s_load_legend;
|
||||
static std::string s_save_legend;
|
||||
static std::string s_prev_legend;
|
||||
static std::string s_next_legend;
|
||||
|
||||
static std::vector<ListEntry> s_slots;
|
||||
static u32 s_current_selection = 0;
|
||||
|
||||
static Common::Timer s_open_timer;
|
||||
static float s_open_time = 0.0f;
|
||||
} // namespace SaveStateSelectorUI
|
||||
|
||||
void SaveStateSelectorUI::Open(float open_time /* = DEFAULT_OPEN_TIME */)
|
||||
{
|
||||
s_open_timer.Reset();
|
||||
s_open_time = open_time;
|
||||
|
||||
if (s_save_state_selector_ui_open)
|
||||
return;
|
||||
|
||||
s_save_state_selector_ui_open = true;
|
||||
RefreshList();
|
||||
RefreshHotkeyLegend();
|
||||
}
|
||||
|
||||
void SaveStateSelectorUI::Close(bool reset_slot)
|
||||
{
|
||||
s_save_state_selector_ui_open = false;
|
||||
s_load_legend = {};
|
||||
s_save_legend = {};
|
||||
s_prev_legend = {};
|
||||
s_next_legend = {};
|
||||
if (reset_slot)
|
||||
s_current_selection = 0;
|
||||
}
|
||||
|
||||
void SaveStateSelectorUI::RefreshList()
|
||||
{
|
||||
s_slots.clear();
|
||||
if (System::IsShutdown())
|
||||
return;
|
||||
|
||||
if (!System::GetGameSerial().empty())
|
||||
{
|
||||
for (s32 i = 1; i <= System::PER_GAME_SAVE_STATE_SLOTS; i++)
|
||||
{
|
||||
std::string path(System::GetGameSaveStateFileName(System::GetGameSerial(), i));
|
||||
std::optional<ExtendedSaveStateInfo> ssi = System::GetExtendedSaveStateInfo(path.c_str());
|
||||
|
||||
ListEntry li;
|
||||
if (ssi)
|
||||
InitializeListEntry(&li, &ssi.value(), std::move(path), i, false);
|
||||
else
|
||||
InitializePlaceholderListEntry(&li, std::move(path), i, false);
|
||||
|
||||
s_slots.push_back(std::move(li));
|
||||
}
|
||||
}
|
||||
|
||||
for (s32 i = 1; i <= System::GLOBAL_SAVE_STATE_SLOTS; i++)
|
||||
{
|
||||
std::string path(System::GetGlobalSaveStateFileName(i));
|
||||
std::optional<ExtendedSaveStateInfo> ssi = System::GetExtendedSaveStateInfo(path.c_str());
|
||||
|
||||
ListEntry li;
|
||||
if (ssi)
|
||||
InitializeListEntry(&li, &ssi.value(), std::move(path), i, true);
|
||||
else
|
||||
InitializePlaceholderListEntry(&li, std::move(path), i, true);
|
||||
|
||||
s_slots.push_back(std::move(li));
|
||||
}
|
||||
|
||||
if (s_slots.empty() || s_current_selection >= s_slots.size())
|
||||
s_current_selection = 0;
|
||||
}
|
||||
|
||||
void SaveStateSelectorUI::DestroyTextures()
|
||||
{
|
||||
Close();
|
||||
|
||||
for (ListEntry& entry : s_slots)
|
||||
entry.preview_texture.reset();
|
||||
}
|
||||
|
||||
void SaveStateSelectorUI::RefreshHotkeyLegend()
|
||||
{
|
||||
auto format_legend_entry = [](std::string_view setting, std::string_view caption) {
|
||||
auto slash_pos = setting.find_first_of('/');
|
||||
if (slash_pos != setting.npos)
|
||||
{
|
||||
setting = setting.substr(slash_pos + 1);
|
||||
}
|
||||
|
||||
return StringUtil::StdStringFromFormat("%.*s - %.*s", static_cast<int>(setting.size()), setting.data(),
|
||||
static_cast<int>(caption.size()), caption.data());
|
||||
};
|
||||
|
||||
s_load_legend = format_legend_entry(Host::GetStringSettingValue("Hotkeys", "LoadSelectedSaveState"),
|
||||
Host::TranslateStdString("SaveStateSelectorUI", "Load"));
|
||||
s_save_legend = format_legend_entry(Host::GetStringSettingValue("Hotkeys", "SaveSelectedSaveState"),
|
||||
Host::TranslateStdString("SaveStateSelectorUI", "Save"));
|
||||
s_prev_legend = format_legend_entry(Host::GetStringSettingValue("Hotkeys", "SelectPreviousSaveStateSlot"),
|
||||
Host::TranslateStdString("SaveStateSelectorUI", "Select Previous"));
|
||||
s_next_legend = format_legend_entry(Host::GetStringSettingValue("Hotkeys", "SelectNextSaveStateSlot"),
|
||||
Host::TranslateStdString("SaveStateSelectorUI", "Select Next"));
|
||||
}
|
||||
|
||||
void SaveStateSelectorUI::SelectNextSlot()
|
||||
{
|
||||
if (!s_save_state_selector_ui_open)
|
||||
Open();
|
||||
|
||||
s_open_timer.Reset();
|
||||
s_current_selection = (s_current_selection == static_cast<u32>(s_slots.size() - 1)) ? 0 : (s_current_selection + 1);
|
||||
}
|
||||
|
||||
void SaveStateSelectorUI::SelectPreviousSlot()
|
||||
{
|
||||
if (!s_save_state_selector_ui_open)
|
||||
Open();
|
||||
|
||||
s_open_timer.Reset();
|
||||
s_current_selection =
|
||||
(s_current_selection == 0) ? (static_cast<u32>(s_slots.size()) - 1u) : (s_current_selection - 1);
|
||||
}
|
||||
|
||||
void SaveStateSelectorUI::InitializeListEntry(ListEntry* li, ExtendedSaveStateInfo* ssi, std::string path, s32 slot,
|
||||
bool global)
|
||||
{
|
||||
li->title = std::move(ssi->title);
|
||||
li->serial = std::move(ssi->serial);
|
||||
li->path = std::move(path);
|
||||
li->formatted_timestamp = fmt::format("{:%c}", fmt::localtime(ssi->timestamp));
|
||||
li->slot = slot;
|
||||
li->global = global;
|
||||
|
||||
li->preview_texture.reset();
|
||||
|
||||
// Might not have a display yet, we're called at startup..
|
||||
if (g_host_display)
|
||||
{
|
||||
if (ssi && !ssi->screenshot_data.empty())
|
||||
{
|
||||
li->preview_texture =
|
||||
g_host_display->CreateTexture(ssi->screenshot_width, ssi->screenshot_height, 1, 1, 1, GPUTexture::Format::RGBA8,
|
||||
ssi->screenshot_data.data(), sizeof(u32) * ssi->screenshot_width, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
li->preview_texture = g_host_display->CreateTexture(
|
||||
Resources::PLACEHOLDER_ICON_WIDTH, Resources::PLACEHOLDER_ICON_HEIGHT, 1, 1, 1, GPUTexture::Format::RGBA8,
|
||||
Resources::PLACEHOLDER_ICON_DATA, sizeof(u32) * Resources::PLACEHOLDER_ICON_WIDTH, false);
|
||||
}
|
||||
|
||||
if (!li->preview_texture)
|
||||
Log_ErrorPrintf("Failed to upload save state image to GPU");
|
||||
}
|
||||
}
|
||||
|
||||
void SaveStateSelectorUI::InitializePlaceholderListEntry(ListEntry* li, std::string path, s32 slot, bool global)
|
||||
{
|
||||
li->title = Host::TranslateStdString("SaveStateSelectorUI", "No Save State");
|
||||
std::string().swap(li->serial);
|
||||
li->path = std::move(path);
|
||||
std::string().swap(li->formatted_timestamp);
|
||||
li->slot = slot;
|
||||
li->global = global;
|
||||
|
||||
if (g_host_display)
|
||||
{
|
||||
li->preview_texture = g_host_display->CreateTexture(
|
||||
Resources::PLACEHOLDER_ICON_WIDTH, Resources::PLACEHOLDER_ICON_HEIGHT, 1, 1, 1, GPUTexture::Format::RGBA8,
|
||||
Resources::PLACEHOLDER_ICON_DATA, sizeof(u32) * Resources::PLACEHOLDER_ICON_WIDTH, false);
|
||||
if (!li->preview_texture)
|
||||
Log_ErrorPrintf("Failed to upload save state image to GPU");
|
||||
}
|
||||
}
|
||||
|
||||
void SaveStateSelectorUI::Draw()
|
||||
{
|
||||
const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x;
|
||||
const float window_width = ImGui::GetIO().DisplaySize.x * (2.0f / 3.0f);
|
||||
const float window_height = ImGui::GetIO().DisplaySize.y * 0.5f;
|
||||
const float rounding = 4.0f * framebuffer_scale;
|
||||
ImGui::SetNextWindowSize(ImVec2(window_width, window_height), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x * 0.5f, ImGui::GetIO().DisplaySize.y * 0.5f),
|
||||
ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.11f, 0.15f, 0.17f, 0.8f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, rounding);
|
||||
|
||||
if (ImGui::Begin("##save_state_selector", nullptr,
|
||||
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoScrollbar))
|
||||
{
|
||||
// Leave 2 lines for the legend
|
||||
const float legend_margin = ImGui::GetFontSize() * 2.0f + ImGui::GetStyle().ItemSpacing.y * 3.0f;
|
||||
const float padding = 10.0f * framebuffer_scale;
|
||||
|
||||
ImGui::BeginChild("##item_list", ImVec2(0, -legend_margin), false,
|
||||
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar);
|
||||
{
|
||||
const ImVec2 image_size = ImVec2(128.0f * framebuffer_scale, (128.0f / (4.0f / 3.0f)) * framebuffer_scale);
|
||||
const float item_height = image_size.y + padding * 2.0f;
|
||||
const float text_indent = image_size.x + padding + padding;
|
||||
|
||||
for (size_t i = 0; i < s_slots.size(); i++)
|
||||
{
|
||||
const ListEntry& entry = s_slots[i];
|
||||
const float y_start = item_height * static_cast<float>(i);
|
||||
|
||||
if (i == s_current_selection)
|
||||
{
|
||||
ImGui::SetCursorPosY(y_start);
|
||||
ImGui::SetScrollHereY();
|
||||
|
||||
const ImVec2 p_start(ImGui::GetCursorScreenPos());
|
||||
const ImVec2 p_end(p_start.x + window_width, p_start.y + item_height);
|
||||
ImGui::GetWindowDrawList()->AddRectFilled(p_start, p_end, ImColor(0.22f, 0.30f, 0.34f, 0.9f), rounding);
|
||||
}
|
||||
|
||||
if (entry.preview_texture)
|
||||
{
|
||||
ImGui::SetCursorPosY(y_start + padding);
|
||||
ImGui::SetCursorPosX(padding);
|
||||
ImGui::Image(entry.preview_texture.get(), image_size);
|
||||
}
|
||||
|
||||
ImGui::SetCursorPosY(y_start + padding);
|
||||
|
||||
ImGui::Indent(text_indent);
|
||||
|
||||
if (entry.global)
|
||||
{
|
||||
ImGui::Text(Host::TranslateString("SaveStateSelectorUI", "Global Slot %d"), entry.slot);
|
||||
}
|
||||
else if (entry.serial.empty())
|
||||
{
|
||||
ImGui::Text(Host::TranslateString("SaveStateSelectorUI", "Game Slot %d"), entry.slot);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::Text(Host::TranslateString("SaveStateSelectorUI", "%s Slot %d"), entry.serial.c_str(), entry.slot);
|
||||
}
|
||||
ImGui::TextUnformatted(entry.title.c_str());
|
||||
ImGui::TextUnformatted(entry.formatted_timestamp.c_str());
|
||||
ImGui::TextUnformatted(entry.path.c_str());
|
||||
|
||||
ImGui::Unindent(text_indent);
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::BeginChild("##legend", ImVec2(0, 0), false,
|
||||
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoScrollbar);
|
||||
{
|
||||
ImGui::SetCursorPosX(padding);
|
||||
ImGui::BeginTable("table", 2);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(s_load_legend.c_str());
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(s_prev_legend.c_str());
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(s_save_legend.c_str());
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(s_next_legend.c_str());
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
// auto-close
|
||||
if (s_open_timer.GetTimeSeconds() >= s_open_time)
|
||||
Close();
|
||||
}
|
||||
|
||||
void SaveStateSelectorUI::LoadCurrentSlot()
|
||||
{
|
||||
if (s_slots.empty() || s_current_selection >= s_slots.size() || s_slots[s_current_selection].path.empty())
|
||||
return;
|
||||
|
||||
System::LoadState(s_slots[s_current_selection].path.c_str());
|
||||
Close();
|
||||
}
|
||||
|
||||
void SaveStateSelectorUI::SaveCurrentSlot()
|
||||
{
|
||||
if (s_slots.empty() || s_current_selection >= s_slots.size() || s_slots[s_current_selection].path.empty())
|
||||
return;
|
||||
|
||||
System::SaveState(s_slots[s_current_selection].path.c_str(), g_settings.create_save_state_backups);
|
||||
Close();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util/imgui_manager.h"
|
||||
|
||||
namespace ImGuiManager {
|
||||
void RenderTextOverlays();
|
||||
void RenderOverlayWindows();
|
||||
}
|
||||
|
||||
namespace SaveStateSelectorUI {
|
||||
|
||||
static constexpr float DEFAULT_OPEN_TIME = 5.0f;
|
||||
|
||||
void Open(float open_time = DEFAULT_OPEN_TIME);
|
||||
void RefreshList();
|
||||
void DestroyTextures();
|
||||
void Close(bool reset_slot = false);
|
||||
|
||||
void SelectNextSlot();
|
||||
void SelectPreviousSlot();
|
||||
|
||||
void LoadCurrentSlot();
|
||||
void SaveCurrentSlot();
|
||||
|
||||
} // namespace SaveStateSelectorUI
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "common/log.h"
|
||||
#include "gpu.h"
|
||||
#include "host.h"
|
||||
#include "host_display.h"
|
||||
#include "util/host_display.h"
|
||||
#include "system.h"
|
||||
#include "util/state_wrapper.h"
|
||||
#include <array>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,4 +12,12 @@ constexpr u32 CROSSHAIR_IMAGE_WIDTH = 96;
|
||||
constexpr u32 CROSSHAIR_IMAGE_HEIGHT = 96;
|
||||
extern const std::array<u32, CROSSHAIR_IMAGE_WIDTH * CROSSHAIR_IMAGE_HEIGHT> CROSSHAIR_IMAGE_DATA;
|
||||
|
||||
constexpr int WINDOW_ICON_WIDTH = 64;
|
||||
constexpr int WINDOW_ICON_HEIGHT = 64;
|
||||
extern unsigned int WINDOW_ICON_DATA[WINDOW_ICON_WIDTH * WINDOW_ICON_HEIGHT];
|
||||
|
||||
constexpr int PLACEHOLDER_ICON_WIDTH = 128;
|
||||
constexpr int PLACEHOLDER_ICON_HEIGHT = 96;
|
||||
extern unsigned int PLACEHOLDER_ICON_DATA[PLACEHOLDER_ICON_WIDTH * PLACEHOLDER_ICON_HEIGHT];
|
||||
|
||||
} // namespace Resources
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include "common/string_util.h"
|
||||
#include "controller.h"
|
||||
#include "host.h"
|
||||
#include "host_display.h"
|
||||
#include "util/host_display.h"
|
||||
#include "host_settings.h"
|
||||
#include "system.h"
|
||||
#include <algorithm>
|
||||
@@ -354,7 +354,6 @@ void Settings::Load(SettingsInterface& si)
|
||||
memory_card_paths[1] = si.GetStringValue("MemoryCards", "Card2Path", "");
|
||||
memory_card_use_playlist_title = si.GetBoolValue("MemoryCards", "UsePlaylistTitle", true);
|
||||
|
||||
#ifdef WITH_CHEEVOS
|
||||
achievements_enabled = si.GetBoolValue("Cheevos", "Enabled", false);
|
||||
achievements_test_mode = si.GetBoolValue("Cheevos", "TestMode", false);
|
||||
achievements_unofficial_test_mode = si.GetBoolValue("Cheevos", "UnofficialTestMode", false);
|
||||
@@ -365,7 +364,6 @@ void Settings::Load(SettingsInterface& si)
|
||||
achievements_notifications = si.GetBoolValue("Cheevos", "Notifications", true);
|
||||
achievements_sound_effects = si.GetBoolValue("Cheevos", "SoundEffects", true);
|
||||
achievements_primed_indicators = si.GetBoolValue("Cheevos", "PrimedIndicators", true);
|
||||
#endif
|
||||
|
||||
log_level = ParseLogLevelName(si.GetStringValue("Logging", "LogLevel", GetLogLevelName(DEFAULT_LOG_LEVEL)).c_str())
|
||||
.value_or(DEFAULT_LOG_LEVEL);
|
||||
@@ -551,7 +549,6 @@ void Settings::Save(SettingsInterface& si) const
|
||||
|
||||
si.SetStringValue("ControllerPorts", "MultitapMode", GetMultitapModeName(multitap_mode));
|
||||
|
||||
#ifdef WITH_CHEEVOS
|
||||
si.SetBoolValue("Cheevos", "Enabled", achievements_enabled);
|
||||
si.SetBoolValue("Cheevos", "TestMode", achievements_test_mode);
|
||||
si.SetBoolValue("Cheevos", "UnofficialTestMode", achievements_unofficial_test_mode);
|
||||
@@ -562,7 +559,6 @@ void Settings::Save(SettingsInterface& si) const
|
||||
si.SetBoolValue("Cheevos", "Notifications", achievements_notifications);
|
||||
si.SetBoolValue("Cheevos", "SoundEffects", achievements_sound_effects);
|
||||
si.SetBoolValue("Cheevos", "PrimedIndicators", achievements_primed_indicators);
|
||||
#endif
|
||||
|
||||
si.SetStringValue("Logging", "LogLevel", GetLogLevelName(log_level));
|
||||
si.SetStringValue("Logging", "LogFilter", log_filter.c_str());
|
||||
|
||||
@@ -178,7 +178,6 @@ struct Settings
|
||||
u32 gpu_fifo_size = DEFAULT_GPU_FIFO_SIZE;
|
||||
TickCount gpu_max_run_ahead = DEFAULT_GPU_MAX_RUN_AHEAD;
|
||||
|
||||
#ifdef WITH_CHEEVOS
|
||||
// achievements
|
||||
bool achievements_enabled = false;
|
||||
bool achievements_test_mode = false;
|
||||
@@ -190,7 +189,6 @@ struct Settings
|
||||
bool achievements_notifications = true;
|
||||
bool achievements_sound_effects = true;
|
||||
bool achievements_primed_indicators = true;
|
||||
#endif
|
||||
|
||||
struct DebugSettings
|
||||
{
|
||||
|
||||
@@ -1,679 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#include "shadergen.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/log.h"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef WITH_OPENGL
|
||||
#include "common/gl/loader.h"
|
||||
#endif
|
||||
|
||||
Log_SetChannel(ShaderGen);
|
||||
|
||||
ShaderGen::ShaderGen(RenderAPI render_api, bool supports_dual_source_blend)
|
||||
: m_render_api(render_api), m_glsl(render_api != RenderAPI::D3D11 && render_api != RenderAPI::D3D12),
|
||||
m_supports_dual_source_blend(supports_dual_source_blend), m_use_glsl_interface_blocks(false)
|
||||
{
|
||||
#if defined(WITH_OPENGL) || defined(WITH_VULKAN)
|
||||
if (m_glsl)
|
||||
{
|
||||
#ifdef WITH_OPENGL
|
||||
if (m_render_api == RenderAPI::OpenGL || m_render_api == RenderAPI::OpenGLES)
|
||||
SetGLSLVersionString();
|
||||
|
||||
m_use_glsl_interface_blocks = (IsVulkan() || GLAD_GL_ES_VERSION_3_2 || GLAD_GL_VERSION_3_2);
|
||||
m_use_glsl_binding_layout = (IsVulkan() || UseGLSLBindingLayout());
|
||||
|
||||
if (m_render_api == RenderAPI::OpenGL)
|
||||
{
|
||||
// SSAA with interface blocks is broken on AMD's OpenGL driver.
|
||||
const char* gl_vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
|
||||
if (std::strcmp(gl_vendor, "ATI Technologies Inc.") == 0)
|
||||
m_use_glsl_interface_blocks = false;
|
||||
}
|
||||
#else
|
||||
m_use_glsl_interface_blocks = true;
|
||||
m_use_glsl_binding_layout = true;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
ShaderGen::~ShaderGen() = default;
|
||||
|
||||
bool ShaderGen::UseGLSLBindingLayout()
|
||||
{
|
||||
#ifdef WITH_OPENGL
|
||||
return (GLAD_GL_ES_VERSION_3_1 || GLAD_GL_VERSION_4_3 ||
|
||||
(GLAD_GL_ARB_explicit_attrib_location && GLAD_GL_ARB_explicit_uniform_location &&
|
||||
GLAD_GL_ARB_shading_language_420pack));
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void ShaderGen::DefineMacro(std::stringstream& ss, const char* name, bool enabled)
|
||||
{
|
||||
ss << "#define " << name << " " << BoolToUInt32(enabled) << "\n";
|
||||
}
|
||||
|
||||
#ifdef WITH_OPENGL
|
||||
void ShaderGen::SetGLSLVersionString()
|
||||
{
|
||||
const char* glsl_version = reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION));
|
||||
const bool glsl_es = (m_render_api == RenderAPI::OpenGLES);
|
||||
Assert(glsl_version != nullptr);
|
||||
|
||||
// Skip any strings in front of the version code.
|
||||
const char* glsl_version_start = glsl_version;
|
||||
while (*glsl_version_start != '\0' && (*glsl_version_start < '0' || *glsl_version_start > '9'))
|
||||
glsl_version_start++;
|
||||
|
||||
int major_version = 0, minor_version = 0;
|
||||
if (std::sscanf(glsl_version_start, "%d.%d", &major_version, &minor_version) == 2)
|
||||
{
|
||||
// Cap at GLSL 4.3, we're not using anything newer for now.
|
||||
if (!glsl_es && (major_version > 4 || (major_version == 4 && minor_version > 30)))
|
||||
{
|
||||
major_version = 4;
|
||||
minor_version = 30;
|
||||
}
|
||||
else if (glsl_es && (major_version > 3 || (major_version == 3 && minor_version > 20)))
|
||||
{
|
||||
major_version = 3;
|
||||
minor_version = 20;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log_ErrorPrintf("Invalid GLSL version string: '%s' ('%s')", glsl_version, glsl_version_start);
|
||||
if (glsl_es)
|
||||
{
|
||||
major_version = 3;
|
||||
minor_version = 0;
|
||||
}
|
||||
m_glsl_version_string = glsl_es ? "300" : "130";
|
||||
}
|
||||
|
||||
char buf[128];
|
||||
std::snprintf(buf, sizeof(buf), "#version %d%02d%s", major_version, minor_version,
|
||||
(glsl_es && major_version >= 3) ? " es" : "");
|
||||
m_glsl_version_string = buf;
|
||||
}
|
||||
#endif
|
||||
|
||||
void ShaderGen::WriteHeader(std::stringstream& ss)
|
||||
{
|
||||
if (m_render_api == RenderAPI::OpenGL || m_render_api == RenderAPI::OpenGLES)
|
||||
ss << m_glsl_version_string << "\n\n";
|
||||
else if (m_render_api == RenderAPI::Vulkan)
|
||||
ss << "#version 450 core\n\n";
|
||||
|
||||
#ifdef WITH_OPENGL
|
||||
// Extension enabling for OpenGL.
|
||||
if (m_render_api == RenderAPI::OpenGLES)
|
||||
{
|
||||
// Enable EXT_blend_func_extended for dual-source blend on OpenGL ES.
|
||||
if (GLAD_GL_EXT_blend_func_extended)
|
||||
ss << "#extension GL_EXT_blend_func_extended : require\n";
|
||||
if (GLAD_GL_ARB_blend_func_extended)
|
||||
ss << "#extension GL_ARB_blend_func_extended : require\n";
|
||||
|
||||
// Test for V3D driver - we have to fudge coordinates slightly.
|
||||
if (std::strstr(reinterpret_cast<const char*>(glGetString(GL_VENDOR)), "Broadcom") &&
|
||||
std::strstr(reinterpret_cast<const char*>(glGetString(GL_RENDERER)), "V3D"))
|
||||
{
|
||||
ss << "#define DRIVER_V3D 1\n";
|
||||
}
|
||||
else if (std::strstr(reinterpret_cast<const char*>(glGetString(GL_RENDERER)), "PowerVR"))
|
||||
{
|
||||
ss << "#define DRIVER_POWERVR 1\n";
|
||||
}
|
||||
}
|
||||
else if (m_render_api == RenderAPI::OpenGL)
|
||||
{
|
||||
// Need extensions for binding layout if GL<4.3.
|
||||
if (m_use_glsl_binding_layout && !GLAD_GL_VERSION_4_3)
|
||||
{
|
||||
ss << "#extension GL_ARB_explicit_attrib_location : require\n";
|
||||
ss << "#extension GL_ARB_explicit_uniform_location : require\n";
|
||||
ss << "#extension GL_ARB_shading_language_420pack : require\n";
|
||||
}
|
||||
|
||||
if (!GLAD_GL_VERSION_3_2)
|
||||
ss << "#extension GL_ARB_uniform_buffer_object : require\n";
|
||||
|
||||
// Enable SSBOs if it's not required by the version.
|
||||
if (!GLAD_GL_VERSION_4_3 && !GLAD_GL_ES_VERSION_3_1 && GLAD_GL_ARB_shader_storage_buffer_object)
|
||||
ss << "#extension GL_ARB_shader_storage_buffer_object : require\n";
|
||||
}
|
||||
#endif
|
||||
|
||||
DefineMacro(ss, "API_OPENGL", m_render_api == RenderAPI::OpenGL);
|
||||
DefineMacro(ss, "API_OPENGL_ES", m_render_api == RenderAPI::OpenGLES);
|
||||
DefineMacro(ss, "API_D3D11", m_render_api == RenderAPI::D3D11);
|
||||
DefineMacro(ss, "API_D3D12", m_render_api == RenderAPI::D3D12);
|
||||
DefineMacro(ss, "API_VULKAN", m_render_api == RenderAPI::Vulkan);
|
||||
|
||||
#ifdef WITH_OPENGL
|
||||
if (m_render_api == RenderAPI::OpenGLES)
|
||||
{
|
||||
ss << "precision highp float;\n";
|
||||
ss << "precision highp int;\n";
|
||||
ss << "precision highp sampler2D;\n";
|
||||
|
||||
if (GLAD_GL_ES_VERSION_3_1)
|
||||
ss << "precision highp sampler2DMS;\n";
|
||||
|
||||
if (GLAD_GL_ES_VERSION_3_2)
|
||||
ss << "precision highp usamplerBuffer;\n";
|
||||
|
||||
ss << "\n";
|
||||
}
|
||||
#endif
|
||||
|
||||
if (m_glsl)
|
||||
{
|
||||
ss << "#define GLSL 1\n";
|
||||
ss << "#define float2 vec2\n";
|
||||
ss << "#define float3 vec3\n";
|
||||
ss << "#define float4 vec4\n";
|
||||
ss << "#define int2 ivec2\n";
|
||||
ss << "#define int3 ivec3\n";
|
||||
ss << "#define int4 ivec4\n";
|
||||
ss << "#define uint2 uvec2\n";
|
||||
ss << "#define uint3 uvec3\n";
|
||||
ss << "#define uint4 uvec4\n";
|
||||
ss << "#define float2x2 mat2\n";
|
||||
ss << "#define float3x3 mat3\n";
|
||||
ss << "#define float4x4 mat4\n";
|
||||
ss << "#define mul(x, y) ((x) * (y))\n";
|
||||
ss << "#define nointerpolation flat\n";
|
||||
ss << "#define frac fract\n";
|
||||
ss << "#define lerp mix\n";
|
||||
|
||||
ss << "#define CONSTANT const\n";
|
||||
ss << "#define GLOBAL\n";
|
||||
ss << "#define FOR_UNROLL for\n";
|
||||
ss << "#define FOR_LOOP for\n";
|
||||
ss << "#define IF_BRANCH if\n";
|
||||
ss << "#define IF_FLATTEN if\n";
|
||||
ss << "#define VECTOR_EQ(a, b) ((a) == (b))\n";
|
||||
ss << "#define VECTOR_NEQ(a, b) ((a) != (b))\n";
|
||||
ss << "#define VECTOR_COMP_EQ(a, b) equal((a), (b))\n";
|
||||
ss << "#define VECTOR_COMP_NEQ(a, b) notEqual((a), (b))\n";
|
||||
ss << "#define SAMPLE_TEXTURE(name, coords) texture(name, coords)\n";
|
||||
ss << "#define SAMPLE_TEXTURE_OFFSET(name, coords, offset) textureOffset(name, coords, offset)\n";
|
||||
ss << "#define SAMPLE_TEXTURE_LEVEL(name, coords, level) textureLod(name, coords, level)\n";
|
||||
ss << "#define SAMPLE_TEXTURE_LEVEL_OFFSET(name, coords, level, offset) textureLod(name, coords, level, offset)\n";
|
||||
ss << "#define LOAD_TEXTURE(name, coords, mip) texelFetch(name, coords, mip)\n";
|
||||
ss << "#define LOAD_TEXTURE_MS(name, coords, sample) texelFetch(name, coords, int(sample))\n";
|
||||
ss << "#define LOAD_TEXTURE_OFFSET(name, coords, mip, offset) texelFetchOffset(name, coords, mip, offset)\n";
|
||||
ss << "#define LOAD_TEXTURE_BUFFER(name, index) texelFetch(name, index)\n";
|
||||
ss << "#define BEGIN_ARRAY(type, size) type[size](\n";
|
||||
ss << "#define END_ARRAY )\n";
|
||||
|
||||
ss << "float saturate(float value) { return clamp(value, 0.0, 1.0); }\n";
|
||||
ss << "float2 saturate(float2 value) { return clamp(value, float2(0.0, 0.0), float2(1.0, 1.0)); }\n";
|
||||
ss << "float3 saturate(float3 value) { return clamp(value, float3(0.0, 0.0, 0.0), float3(1.0, 1.0, 1.0)); }\n";
|
||||
ss << "float4 saturate(float4 value) { return clamp(value, float4(0.0, 0.0, 0.0, 0.0), float4(1.0, 1.0, 1.0, "
|
||||
"1.0)); }\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "#define HLSL 1\n";
|
||||
ss << "#define roundEven round\n";
|
||||
ss << "#define mix lerp\n";
|
||||
ss << "#define fract frac\n";
|
||||
ss << "#define vec2 float2\n";
|
||||
ss << "#define vec3 float3\n";
|
||||
ss << "#define vec4 float4\n";
|
||||
ss << "#define ivec2 int2\n";
|
||||
ss << "#define ivec3 int3\n";
|
||||
ss << "#define ivec4 int4\n";
|
||||
ss << "#define uivec2 uint2\n";
|
||||
ss << "#define uivec3 uint3\n";
|
||||
ss << "#define uivec4 uint4\n";
|
||||
ss << "#define mat2 float2x2\n";
|
||||
ss << "#define mat3 float3x3\n";
|
||||
ss << "#define mat4 float4x4\n";
|
||||
ss << "#define CONSTANT static const\n";
|
||||
ss << "#define GLOBAL static\n";
|
||||
ss << "#define FOR_UNROLL [unroll] for\n";
|
||||
ss << "#define FOR_LOOP [loop] for\n";
|
||||
ss << "#define IF_BRANCH [branch] if\n";
|
||||
ss << "#define IF_FLATTEN [flatten] if\n";
|
||||
ss << "#define VECTOR_EQ(a, b) (all((a) == (b)))\n";
|
||||
ss << "#define VECTOR_NEQ(a, b) (any((a) != (b)))\n";
|
||||
ss << "#define VECTOR_COMP_EQ(a, b) ((a) == (b))\n";
|
||||
ss << "#define VECTOR_COMP_NEQ(a, b) ((a) != (b))\n";
|
||||
ss << "#define SAMPLE_TEXTURE(name, coords) name.Sample(name##_ss, coords)\n";
|
||||
ss << "#define SAMPLE_TEXTURE_OFFSET(name, coords, offset) name.Sample(name##_ss, coords, offset)\n";
|
||||
ss << "#define SAMPLE_TEXTURE_LEVEL(name, coords, level) name.SampleLevel(name##_ss, coords, level)\n";
|
||||
ss << "#define SAMPLE_TEXTURE_LEVEL_OFFSET(name, coords, level, offset) name.SampleLevel(name##_ss, coords, level, "
|
||||
"offset)\n";
|
||||
ss << "#define LOAD_TEXTURE(name, coords, mip) name.Load(int3(coords, mip))\n";
|
||||
ss << "#define LOAD_TEXTURE_MS(name, coords, sample) name.Load(coords, sample)\n";
|
||||
ss << "#define LOAD_TEXTURE_OFFSET(name, coords, mip, offset) name.Load(int3(coords, mip), offset)\n";
|
||||
ss << "#define LOAD_TEXTURE_BUFFER(name, index) name.Load(index)\n";
|
||||
ss << "#define BEGIN_ARRAY(type, size) {\n";
|
||||
ss << "#define END_ARRAY }\n";
|
||||
}
|
||||
|
||||
ss << "\n";
|
||||
}
|
||||
|
||||
void ShaderGen::WriteUniformBufferDeclaration(std::stringstream& ss, bool push_constant_on_vulkan)
|
||||
{
|
||||
if (IsVulkan())
|
||||
{
|
||||
if (push_constant_on_vulkan)
|
||||
ss << "layout(push_constant) uniform PushConstants\n";
|
||||
else
|
||||
ss << "layout(std140, set = 0, binding = 0) uniform UBOBlock\n";
|
||||
}
|
||||
else if (m_glsl)
|
||||
{
|
||||
if (m_use_glsl_binding_layout)
|
||||
ss << "layout(std140, binding = 1) uniform UBOBlock\n";
|
||||
else
|
||||
ss << "layout(std140) uniform UBOBlock\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "cbuffer UBOBlock : register(b0)\n";
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderGen::DeclareUniformBuffer(std::stringstream& ss, const std::initializer_list<const char*>& members,
|
||||
bool push_constant_on_vulkan)
|
||||
{
|
||||
WriteUniformBufferDeclaration(ss, push_constant_on_vulkan);
|
||||
|
||||
ss << "{\n";
|
||||
for (const char* member : members)
|
||||
ss << member << ";\n";
|
||||
ss << "};\n\n";
|
||||
}
|
||||
|
||||
void ShaderGen::DeclareTexture(std::stringstream& ss, const char* name, u32 index, bool multisampled /* = false */)
|
||||
{
|
||||
if (m_glsl)
|
||||
{
|
||||
if (IsVulkan())
|
||||
ss << "layout(set = 0, binding = " << (index + 1u) << ") ";
|
||||
else if (m_use_glsl_binding_layout)
|
||||
ss << "layout(binding = " << index << ") ";
|
||||
|
||||
ss << "uniform " << (multisampled ? "sampler2DMS " : "sampler2D ") << name << ";\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << (multisampled ? "Texture2DMS<float4> " : "Texture2D ") << name << " : register(t" << index << ");\n";
|
||||
ss << "SamplerState " << name << "_ss : register(s" << index << ");\n";
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderGen::DeclareTextureBuffer(std::stringstream& ss, const char* name, u32 index, bool is_int, bool is_unsigned)
|
||||
{
|
||||
if (m_glsl)
|
||||
{
|
||||
if (IsVulkan())
|
||||
ss << "layout(set = 0, binding = " << index << ") ";
|
||||
else if (m_use_glsl_binding_layout)
|
||||
ss << "layout(binding = " << index << ") ";
|
||||
|
||||
ss << "uniform " << (is_int ? (is_unsigned ? "u" : "i") : "") << "samplerBuffer " << name << ";\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "Buffer<" << (is_int ? (is_unsigned ? "uint4" : "int4") : "float4") << "> " << name << " : register(t"
|
||||
<< index << ");\n";
|
||||
}
|
||||
}
|
||||
|
||||
const char* ShaderGen::GetInterpolationQualifier(bool interface_block, bool centroid_interpolation,
|
||||
bool sample_interpolation, bool is_out) const
|
||||
{
|
||||
#ifdef WITH_OPENGL
|
||||
const bool shading_language_420pack = GLAD_GL_ARB_shading_language_420pack;
|
||||
#else
|
||||
const bool shading_language_420pack = false;
|
||||
#endif
|
||||
if (m_glsl && interface_block && (!IsVulkan() && !shading_language_420pack))
|
||||
{
|
||||
return (sample_interpolation ? (is_out ? "sample out " : "sample in ") :
|
||||
(centroid_interpolation ? (is_out ? "centroid out " : "centroid in ") : ""));
|
||||
}
|
||||
else
|
||||
{
|
||||
return (sample_interpolation ? "sample " : (centroid_interpolation ? "centroid " : ""));
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderGen::DeclareVertexEntryPoint(
|
||||
std::stringstream& ss, const std::initializer_list<const char*>& attributes, u32 num_color_outputs,
|
||||
u32 num_texcoord_outputs, const std::initializer_list<std::pair<const char*, const char*>>& additional_outputs,
|
||||
bool declare_vertex_id /* = false */, const char* output_block_suffix /* = "" */, bool msaa /* = false */,
|
||||
bool ssaa /* = false */, bool noperspective_color /* = false */)
|
||||
{
|
||||
if (m_glsl)
|
||||
{
|
||||
if (m_use_glsl_binding_layout)
|
||||
{
|
||||
u32 attribute_counter = 0;
|
||||
for (const char* attribute : attributes)
|
||||
{
|
||||
ss << "layout(location = " << attribute_counter << ") in " << attribute << ";\n";
|
||||
attribute_counter++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const char* attribute : attributes)
|
||||
ss << "in " << attribute << ";\n";
|
||||
}
|
||||
|
||||
if (m_use_glsl_interface_blocks)
|
||||
{
|
||||
const char* qualifier = GetInterpolationQualifier(true, msaa, ssaa, true);
|
||||
|
||||
if (IsVulkan())
|
||||
ss << "layout(location = 0) ";
|
||||
|
||||
ss << "out VertexData" << output_block_suffix << " {\n";
|
||||
for (u32 i = 0; i < num_color_outputs; i++)
|
||||
ss << " " << (noperspective_color ? "noperspective " : "") << qualifier << "float4 v_col" << i << ";\n";
|
||||
|
||||
for (u32 i = 0; i < num_texcoord_outputs; i++)
|
||||
ss << " " << qualifier << "float2 v_tex" << i << ";\n";
|
||||
|
||||
for (const auto& [qualifiers, name] : additional_outputs)
|
||||
{
|
||||
const char* qualifier_to_use = (std::strlen(qualifiers) > 0) ? qualifiers : qualifier;
|
||||
ss << " " << qualifier_to_use << " " << name << ";\n";
|
||||
}
|
||||
ss << "};\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* qualifier = GetInterpolationQualifier(false, msaa, ssaa, true);
|
||||
|
||||
for (u32 i = 0; i < num_color_outputs; i++)
|
||||
ss << qualifier << (noperspective_color ? "noperspective " : "") << "out float4 v_col" << i << ";\n";
|
||||
|
||||
for (u32 i = 0; i < num_texcoord_outputs; i++)
|
||||
ss << qualifier << "out float2 v_tex" << i << ";\n";
|
||||
|
||||
for (const auto& [qualifiers, name] : additional_outputs)
|
||||
{
|
||||
const char* qualifier_to_use = (std::strlen(qualifiers) > 0) ? qualifiers : qualifier;
|
||||
ss << qualifier_to_use << " out " << name << ";\n";
|
||||
}
|
||||
}
|
||||
|
||||
ss << "#define v_pos gl_Position\n\n";
|
||||
if (declare_vertex_id)
|
||||
{
|
||||
if (IsVulkan())
|
||||
ss << "#define v_id uint(gl_VertexIndex)\n";
|
||||
else
|
||||
ss << "#define v_id uint(gl_VertexID)\n";
|
||||
}
|
||||
|
||||
ss << "\n";
|
||||
ss << "void main()\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* qualifier = GetInterpolationQualifier(false, msaa, ssaa, true);
|
||||
|
||||
ss << "void main(\n";
|
||||
|
||||
if (declare_vertex_id)
|
||||
ss << " in uint v_id : SV_VertexID,\n";
|
||||
|
||||
u32 attribute_counter = 0;
|
||||
for (const char* attribute : attributes)
|
||||
{
|
||||
ss << " in " << attribute << " : ATTR" << attribute_counter << ",\n";
|
||||
attribute_counter++;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < num_color_outputs; i++)
|
||||
ss << " " << qualifier << (noperspective_color ? "noperspective " : "") << "out float4 v_col" << i << " : COLOR"
|
||||
<< i << ",\n";
|
||||
|
||||
for (u32 i = 0; i < num_texcoord_outputs; i++)
|
||||
ss << " " << qualifier << "out float2 v_tex" << i << " : TEXCOORD" << i << ",\n";
|
||||
|
||||
u32 additional_counter = num_texcoord_outputs;
|
||||
for (const auto& [qualifiers, name] : additional_outputs)
|
||||
{
|
||||
const char* qualifier_to_use = (std::strlen(qualifiers) > 0) ? qualifiers : qualifier;
|
||||
ss << " " << qualifier_to_use << " out " << name << " : TEXCOORD" << additional_counter << ",\n";
|
||||
additional_counter++;
|
||||
}
|
||||
|
||||
ss << " out float4 v_pos : SV_Position)\n";
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderGen::DeclareFragmentEntryPoint(
|
||||
std::stringstream& ss, u32 num_color_inputs, u32 num_texcoord_inputs,
|
||||
const std::initializer_list<std::pair<const char*, const char*>>& additional_inputs,
|
||||
bool declare_fragcoord /* = false */, u32 num_color_outputs /* = 1 */, bool depth_output /* = false */,
|
||||
bool msaa /* = false */, bool ssaa /* = false */, bool declare_sample_id /* = false */,
|
||||
bool noperspective_color /* = false */)
|
||||
{
|
||||
if (m_glsl)
|
||||
{
|
||||
if (m_use_glsl_interface_blocks)
|
||||
{
|
||||
const char* qualifier = GetInterpolationQualifier(true, msaa, ssaa, false);
|
||||
|
||||
if (IsVulkan())
|
||||
ss << "layout(location = 0) ";
|
||||
|
||||
ss << "in VertexData {\n";
|
||||
for (u32 i = 0; i < num_color_inputs; i++)
|
||||
ss << " " << qualifier << (noperspective_color ? "noperspective " : "") << "float4 v_col" << i << ";\n";
|
||||
|
||||
for (u32 i = 0; i < num_texcoord_inputs; i++)
|
||||
ss << " " << qualifier << "float2 v_tex" << i << ";\n";
|
||||
|
||||
for (const auto& [qualifiers, name] : additional_inputs)
|
||||
{
|
||||
const char* qualifier_to_use = (std::strlen(qualifiers) > 0) ? qualifiers : qualifier;
|
||||
ss << " " << qualifier_to_use << " " << name << ";\n";
|
||||
}
|
||||
ss << "};\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* qualifier = GetInterpolationQualifier(false, msaa, ssaa, false);
|
||||
|
||||
for (u32 i = 0; i < num_color_inputs; i++)
|
||||
ss << qualifier << (noperspective_color ? "noperspective " : "") << "in float4 v_col" << i << ";\n";
|
||||
|
||||
for (u32 i = 0; i < num_texcoord_inputs; i++)
|
||||
ss << qualifier << "in float2 v_tex" << i << ";\n";
|
||||
|
||||
for (const auto& [qualifiers, name] : additional_inputs)
|
||||
{
|
||||
const char* qualifier_to_use = (std::strlen(qualifiers) > 0) ? qualifiers : qualifier;
|
||||
ss << qualifier_to_use << " in " << name << ";\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (declare_fragcoord)
|
||||
ss << "#define v_pos gl_FragCoord\n";
|
||||
|
||||
if (declare_sample_id)
|
||||
ss << "#define f_sample_index uint(gl_SampleID)\n";
|
||||
|
||||
if (depth_output)
|
||||
ss << "#define o_depth gl_FragDepth\n";
|
||||
|
||||
if (m_use_glsl_binding_layout)
|
||||
{
|
||||
if (m_supports_dual_source_blend)
|
||||
{
|
||||
for (u32 i = 0; i < num_color_outputs; i++)
|
||||
ss << "layout(location = 0, index = " << i << ") out float4 o_col" << i << ";\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert(num_color_outputs <= 1);
|
||||
for (u32 i = 0; i < num_color_outputs; i++)
|
||||
ss << "layout(location = " << i << ") out float4 o_col" << i << ";\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 i = 0; i < num_color_outputs; i++)
|
||||
ss << "out float4 o_col" << i << ";\n";
|
||||
}
|
||||
|
||||
ss << "\n";
|
||||
|
||||
ss << "void main()\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* qualifier = GetInterpolationQualifier(false, msaa, ssaa, false);
|
||||
|
||||
ss << "void main(\n";
|
||||
|
||||
for (u32 i = 0; i < num_color_inputs; i++)
|
||||
ss << " " << qualifier << (noperspective_color ? "noperspective " : "") << "in float4 v_col" << i << " : COLOR"
|
||||
<< i << ",\n";
|
||||
|
||||
for (u32 i = 0; i < num_texcoord_inputs; i++)
|
||||
ss << " " << qualifier << "in float2 v_tex" << i << " : TEXCOORD" << i << ",\n";
|
||||
|
||||
u32 additional_counter = num_texcoord_inputs;
|
||||
for (const auto& [qualifiers, name] : additional_inputs)
|
||||
{
|
||||
const char* qualifier_to_use = (std::strlen(qualifiers) > 0) ? qualifiers : qualifier;
|
||||
ss << " " << qualifier_to_use << " in " << name << " : TEXCOORD" << additional_counter << ",\n";
|
||||
additional_counter++;
|
||||
}
|
||||
|
||||
if (declare_fragcoord)
|
||||
ss << " in float4 v_pos : SV_Position,\n";
|
||||
if (declare_sample_id)
|
||||
ss << " in uint f_sample_index : SV_SampleIndex,\n";
|
||||
|
||||
if (depth_output)
|
||||
{
|
||||
ss << " out float o_depth : SV_Depth";
|
||||
if (num_color_outputs > 0)
|
||||
ss << ",\n";
|
||||
else
|
||||
ss << ")\n";
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < num_color_outputs; i++)
|
||||
{
|
||||
ss << " out float4 o_col" << i << " : SV_Target" << i;
|
||||
|
||||
if (i == (num_color_outputs - 1))
|
||||
ss << ")\n";
|
||||
else
|
||||
ss << ",\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string ShaderGen::GenerateScreenQuadVertexShader()
|
||||
{
|
||||
std::stringstream ss;
|
||||
WriteHeader(ss);
|
||||
DeclareVertexEntryPoint(ss, {}, 0, 1, {}, true);
|
||||
ss << R"(
|
||||
{
|
||||
v_tex0 = float2(float((v_id << 1) & 2u), float(v_id & 2u));
|
||||
v_pos = float4(v_tex0 * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);
|
||||
#if API_OPENGL || API_OPENGL_ES || API_VULKAN
|
||||
v_pos.y = -v_pos.y;
|
||||
#endif
|
||||
}
|
||||
)";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string ShaderGen::GenerateUVQuadVertexShader()
|
||||
{
|
||||
std::stringstream ss;
|
||||
WriteHeader(ss);
|
||||
DeclareUniformBuffer(ss, {"float2 u_uv_min", "float2 u_uv_max"}, true);
|
||||
DeclareVertexEntryPoint(ss, {}, 0, 1, {}, true);
|
||||
ss << R"(
|
||||
{
|
||||
v_tex0 = float2(float((v_id << 1) & 2u), float(v_id & 2u));
|
||||
v_pos = float4(v_tex0 * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);
|
||||
v_tex0 = u_uv_min + (u_uv_max - u_uv_min) * v_tex0;
|
||||
#if API_OPENGL || API_OPENGL_ES || API_VULKAN
|
||||
v_pos.y = -v_pos.y;
|
||||
#endif
|
||||
}
|
||||
)";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string ShaderGen::GenerateFillFragmentShader()
|
||||
{
|
||||
std::stringstream ss;
|
||||
WriteHeader(ss);
|
||||
DeclareUniformBuffer(ss, {"float4 u_fill_color"}, true);
|
||||
DeclareFragmentEntryPoint(ss, 0, 1, {}, false, 1, true);
|
||||
|
||||
ss << R"(
|
||||
{
|
||||
o_col0 = u_fill_color;
|
||||
o_depth = u_fill_color.a;
|
||||
}
|
||||
)";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string ShaderGen::GenerateCopyFragmentShader()
|
||||
{
|
||||
std::stringstream ss;
|
||||
WriteHeader(ss);
|
||||
DeclareUniformBuffer(ss, {"float4 u_src_rect"}, true);
|
||||
DeclareTexture(ss, "samp0", 0);
|
||||
DeclareFragmentEntryPoint(ss, 0, 1, {}, false, 1);
|
||||
|
||||
ss << R"(
|
||||
{
|
||||
float2 coords = u_src_rect.xy + v_tex0 * u_src_rect.zw;
|
||||
o_col0 = SAMPLE_TEXTURE(samp0, coords);
|
||||
}
|
||||
)";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string ShaderGen::GenerateSampleFragmentShader()
|
||||
{
|
||||
std::stringstream ss;
|
||||
WriteHeader(ss);
|
||||
DeclareTexture(ss, "samp0", 0);
|
||||
DeclareFragmentEntryPoint(ss, 0, 1, {}, false, 1);
|
||||
|
||||
ss << R"(
|
||||
{
|
||||
o_col0 = SAMPLE_TEXTURE(samp0, v_tex0);
|
||||
}
|
||||
)";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#pragma once
|
||||
#include "gpu_hw.h"
|
||||
#include "host_display.h"
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
class ShaderGen
|
||||
{
|
||||
public:
|
||||
ShaderGen(RenderAPI render_api, bool supports_dual_source_blend);
|
||||
~ShaderGen();
|
||||
|
||||
static bool UseGLSLBindingLayout();
|
||||
|
||||
std::string GenerateScreenQuadVertexShader();
|
||||
std::string GenerateUVQuadVertexShader();
|
||||
std::string GenerateFillFragmentShader();
|
||||
std::string GenerateCopyFragmentShader();
|
||||
std::string GenerateSampleFragmentShader();
|
||||
|
||||
protected:
|
||||
ALWAYS_INLINE bool IsVulkan() const { return (m_render_api == RenderAPI::Vulkan); }
|
||||
|
||||
const char* GetInterpolationQualifier(bool interface_block, bool centroid_interpolation, bool sample_interpolation,
|
||||
bool is_out) const;
|
||||
|
||||
#ifdef WITH_OPENGL
|
||||
void SetGLSLVersionString();
|
||||
#endif
|
||||
|
||||
void DefineMacro(std::stringstream& ss, const char* name, bool enabled);
|
||||
void WriteHeader(std::stringstream& ss);
|
||||
void WriteUniformBufferDeclaration(std::stringstream& ss, bool push_constant_on_vulkan);
|
||||
void DeclareUniformBuffer(std::stringstream& ss, const std::initializer_list<const char*>& members,
|
||||
bool push_constant_on_vulkan);
|
||||
void DeclareTexture(std::stringstream& ss, const char* name, u32 index, bool multisampled = false);
|
||||
void DeclareTextureBuffer(std::stringstream& ss, const char* name, u32 index, bool is_int, bool is_unsigned);
|
||||
void DeclareVertexEntryPoint(std::stringstream& ss, const std::initializer_list<const char*>& attributes,
|
||||
u32 num_color_outputs, u32 num_texcoord_outputs,
|
||||
const std::initializer_list<std::pair<const char*, const char*>>& additional_outputs,
|
||||
bool declare_vertex_id = false, const char* output_block_suffix = "", bool msaa = false,
|
||||
bool ssaa = false, bool noperspective_color = false);
|
||||
void DeclareFragmentEntryPoint(std::stringstream& ss, u32 num_color_inputs, u32 num_texcoord_inputs,
|
||||
const std::initializer_list<std::pair<const char*, const char*>>& additional_inputs,
|
||||
bool declare_fragcoord = false, u32 num_color_outputs = 1, bool depth_output = false,
|
||||
bool msaa = false, bool ssaa = false, bool declare_sample_id = false,
|
||||
bool noperspective_color = false);
|
||||
|
||||
RenderAPI m_render_api;
|
||||
bool m_glsl;
|
||||
bool m_supports_dual_source_blend;
|
||||
bool m_use_glsl_interface_blocks;
|
||||
bool m_use_glsl_binding_layout;
|
||||
|
||||
std::string m_glsl_version_string;
|
||||
};
|
||||
+1
-1
@@ -25,7 +25,7 @@
|
||||
#include "gpu.h"
|
||||
#include "gte.h"
|
||||
#include "host.h"
|
||||
#include "host_display.h"
|
||||
#include "util/host_display.h"
|
||||
#include "host_interface_progress_callback.h"
|
||||
#include "host_settings.h"
|
||||
#include "interrupt_controller.h"
|
||||
|
||||
Reference in New Issue
Block a user