Move frontend-common to util/core

This commit is contained in:
Stenzek
2023-08-13 16:28:28 +10:00
parent 5b980dafa5
commit bb60170d9a
144 changed files with 2506 additions and 3180 deletions
+25 -5
View File
@@ -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
+2
View File
@@ -4,6 +4,8 @@
#pragma once
#include "common/types.h"
#include <string>
class StateWrapper;
class CDImage;
+178
View File
@@ -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
+48
View File
@@ -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
View File
@@ -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
View File
@@ -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>
+11 -4
View File
@@ -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
+70
View File
@@ -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
+118
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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>
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -3,7 +3,7 @@
#pragma once
#include "gpu_hw.h"
#include "shadergen.h"
#include "util/shadergen.h"
class GPU_HW_ShaderGen : public ShaderGen
{
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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>
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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"
-702
View File
@@ -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;
}
-276
View File
@@ -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
+192
View File
@@ -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);
}
+885
View File
@@ -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();
}
+28
View File
@@ -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
+1 -1
View File
@@ -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
+8
View File
@@ -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
+1 -5
View File
@@ -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());
-2
View File
@@ -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
{
-679
View File
@@ -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();
}
-59
View File
@@ -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
View File
@@ -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"