Compare commits

...
9 Commits
Author SHA1 Message Date
refractionpcsx2 e0abded5a3 GS/TC: Properly increase iterator on preload target loop 2026-09-14 08:09:09 -04:00
TheLastRar 7392b3b965 FSUI: Handle non-ascii date and time strings on Windows 2026-09-14 09:22:10 +02:00
TheLastRar c22fccbbc9 GameList: Handle non-ascii date strings on Windows 2026-09-14 09:22:10 +02:00
pandubz a05bb033ec Pad: Add option to correct diagonal scale on analog sticks.
Adds a new option to scale analog stick diagonal inputs, correcting for the drop off caused by modern controllers having a square pickup zone as opposed to the DS2 which was round and could sustain full input on diagonals.

A possibly cleaner alternative to axis scaling, so full input can still be achieved on diagonals without having to crank axis scaling and causing cardinal directions to max out noticeably before hitting the actual edge of the stick housing.
2026-09-14 09:06:23 +02:00
SternXD 1cd4392170 FullscreenUI: Add media capture settings
Signed-off-by: SternXD <stern@sidestore.io>
2026-09-14 08:59:26 +02:00
SternXD 9b37fc68a1 GS/FullscreenUI: Add video recording option to pause menu
Signed-off-by: SternXD <stern@sidestore.io>
2026-09-14 08:59:26 +02:00
PCSX2 Bot a7ed3190e0 [ci skip] Qt: Update Base Translation. 2026-09-14 06:28:22 +02:00
refractionpcsx2 489778cf6d GS: Make sure hardware local->local transfers get logged 2026-09-14 06:22:27 +02:00
refractionpcsx2 112cc226b7 GS/TC: Update dirty rects on special handling local->local transfers 2026-09-14 06:22:27 +02:00
27 changed files with 389 additions and 38 deletions
-7
View File
@@ -13376,13 +13376,6 @@ which produces much nicer sound during fast-forward/slowdown.</source>
<source>Spin GPU During Readbacks is enabled, but calibrated timestamps are unavailable. This might be really slow.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../pcsx2/GS/Renderers/DX11/D3D.cpp" line="389"/>
<source>The Vulkan graphics API was automatically selected, but no compatible devices were found.
You should update all graphics drivers in your system, including any integrated GPUs
to use the Vulkan renderer.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../pcsx2/MTGS.cpp" line="1002"/>
<source>Switching to Software Renderer...</source>
+16 -11
View File
@@ -523,6 +523,21 @@ void GSEndCapture()
g_gs_renderer->EndCapture();
}
void GSToggleVideoCapture()
{
if (!g_gs_renderer)
return;
if (GSCapture::IsCapturing())
{
g_gs_renderer->EndCapture();
return;
}
std::string filename(fmt::format("{}.{}", GSGetBaseVideoFilename(), GSConfig.CaptureContainer));
g_gs_renderer->BeginCapture(std::move(filename));
}
void GSPresentCurrentFrame()
{
g_gs_renderer->PresentCurrentFrame();
@@ -1217,17 +1232,7 @@ BEGIN_HOTKEY_LIST(g_gs_hotkeys){"Screenshot", TRANSLATE_NOOP("Hotkeys", "Graphic
[](s32 pressed) {
if (!pressed)
{
if (GSCapture::IsCapturing())
{
MTGS::RunOnGSThread([]() { g_gs_renderer->EndCapture(); });
MTGS::WaitGS(false, false, false);
return;
}
MTGS::RunOnGSThread([]() {
std::string filename(fmt::format("{}.{}", GSGetBaseVideoFilename(), GSConfig.CaptureContainer));
g_gs_renderer->BeginCapture(std::move(filename));
});
MTGS::RunOnGSThread(&GSToggleVideoCapture);
// Sync GS thread. We want to start adding audio at the same time as video.
MTGS::WaitGS(false, false, false);
+1
View File
@@ -81,6 +81,7 @@ void GSDumpSavedMetrics();
bool GSIsSavingMetrics();
bool GSBeginCapture(std::string filename);
void GSEndCapture();
void GSToggleVideoCapture();
void GSPresentCurrentFrame();
void GSThrottlePresentation();
void GSGameChanged();
+7 -4
View File
@@ -1359,8 +1359,9 @@ void GSState::DumpTransferImages()
transfer.rect.x, transfer.rect.y, transfer.rect.z, transfer.rect.w);
}
m_mem.SaveBMP(filename, transfer.blit.DBP, transfer.blit.DBW, transfer.blit.DPSM,
transfer.rect.width(), transfer.rect.height(), transfer.rect.x, transfer.rect.y);
if (!transfer.was_hardware_only)
m_mem.SaveBMP(filename, transfer.blit.DBP, transfer.blit.DBW, transfer.blit.DPSM,
transfer.rect.width(), transfer.rect.height(), transfer.rect.x, transfer.rect.y);
}
}
@@ -2932,11 +2933,12 @@ void GSState::Write(const u8* mem, int len)
m_draw_transfers.pop_back();
transfer.rect = transfer.rect.runion(r);
transfer.draw = s_n;
transfer.was_hardware_only = false;
m_draw_transfers.push_back(transfer);
}
else
{
const GSUploadQueue new_transfer = {blit, s_n, r, EEGS_TransferType::EE_to_GS};
const GSUploadQueue new_transfer = {blit, s_n, r, EEGS_TransferType::EE_to_GS, false};
m_draw_transfers.push_back(new_transfer);
}
@@ -3131,11 +3133,12 @@ void GSState::Move()
m_draw_transfers.pop_back();
transfer.rect = transfer.rect.runion(r);
transfer.draw = s_n;
transfer.was_hardware_only = false;
m_draw_transfers.push_back(transfer);
}
else
{
const GSUploadQueue new_transfer = {m_env.BITBLTBUF, s_n, r, EEGS_TransferType::GS_to_GS};
const GSUploadQueue new_transfer = {m_env.BITBLTBUF, s_n, r, EEGS_TransferType::GS_to_GS, false};
m_draw_transfers.push_back(new_transfer);
}
+1
View File
@@ -269,6 +269,7 @@ public:
u64 draw;
GSVector4i rect;
EEGS_TransferType transfer_type;
bool was_hardware_only;
};
enum NoGapsType
+19
View File
@@ -2364,6 +2364,23 @@ void GSRendererHW::Move()
if (g_texture_cache->Move(m_env.BITBLTBUF.SBP, m_env.BITBLTBUF.SBW, m_env.BITBLTBUF.SPSM, sx, sy,
m_env.BITBLTBUF.DBP, m_env.BITBLTBUF.DBW, m_env.BITBLTBUF.DPSM, dx, dy, w, h))
{
// Store the transfer for preloading new RT's.
if ((m_draw_transfers.size() > 0 && m_env.BITBLTBUF.DBP == m_draw_transfers.back().blit.DBP && m_draw_transfers.back().transfer_type == EEGS_TransferType::GS_to_GS))
{
// Same BP, let's update the rect.
GSUploadQueue transfer = m_draw_transfers.back();
m_draw_transfers.pop_back();
transfer.rect = transfer.rect.runion(GSVector4i(dx, dy, dx + w, dy + h));
transfer.draw = s_n;
transfer.was_hardware_only = true;
m_draw_transfers.push_back(transfer);
}
else
{
const GSUploadQueue new_transfer = {m_env.BITBLTBUF, s_n, GSVector4i(dx, dy, dx + w, dy + h), EEGS_TransferType::GS_to_GS, true};
m_draw_transfers.push_back(new_transfer);
}
m_env.TRXDIR.XDIR = 3;
// Handled entirely in TC, no need to update local memory.
return;
@@ -10321,6 +10338,7 @@ bool GSRendererHW::TryGSMemClear(bool no_rt, bool preserve_rt, bool invalidate_r
clear_queue.blit.DBP = m_cached_ctx.FRAME.Block();
clear_queue.blit.DBW = m_cached_ctx.FRAME.FBW;
clear_queue.blit.DPSM = m_cached_ctx.FRAME.PSM;
clear_queue.was_hardware_only = false;
m_draw_transfers.push_back(clear_queue);
}
else
@@ -10351,6 +10369,7 @@ bool GSRendererHW::TryGSMemClear(bool no_rt, bool preserve_rt, bool invalidate_r
clear_queue.blit.DBP = m_cached_ctx.ZBUF.Block();
clear_queue.blit.DBW = m_cached_ctx.FRAME.FBW;
clear_queue.blit.DPSM = m_cached_ctx.ZBUF.PSM;
clear_queue.was_hardware_only = false;
m_draw_transfers.push_back(clear_queue);
}
}
@@ -570,6 +570,7 @@ bool GSRendererHWFunctions::SwPrimRender(GSRendererHW& hw, bool invalidate_tc, b
uq.blit.DPSM = hw.m_cached_ctx.FRAME.PSM;
uq.draw = GSState::s_n;
uq.rect = bbox;
uq.was_hardware_only = false;
hw.m_draw_transfers.push_back(uq);
}
+17
View File
@@ -4105,6 +4105,12 @@ GSTextureCache::Target* GSTextureCache::LookupDisplayTarget(GIFRegTEX0 TEX0, con
if (last_draw - iter->draw > 500)
break;
if (iter->was_hardware_only)
{
++iter;
continue;
}
const u32 transfer_end = GSLocalMemory::GetUnwrappedEndBlockAddress(iter->blit.DBP, iter->blit.DBW, iter->blit.DPSM, iter->rect);
// If the format, and location doesn't overlap
@@ -5653,6 +5659,8 @@ bool GSTextureCache::ShuffleMove(u32 BP, u32 BW, u32 PSM, int sx, int sy, int dx
if (read_ba || !write_rg)
tgt->UnscaleRTAlpha();
tgt->Update();
GSHWDrawConfig& config = GSRendererHW::GetInstance()->BeginHLEHardwareDraw(tgt->m_texture, nullptr, tgt->m_scale, tgt->m_texture, tgt->m_scale, bbox);
config.colormask.wrgba = (write_rg ? (1 | 2) : (4 | 8));
config.ps.process_ba = read_ba ? 1 : 0;
@@ -5734,6 +5742,15 @@ bool GSTextureCache::PageMove(u32 SBP, u32 DBP, u32 BW, u32 PSM, int sx, int sy,
return false;
}
// We don't want to copy "old" data that the game has overwritten with writes,
// so flush any overlapping dirty area.
// We pass that this is an invalidation to the translate function just to get a rough rect, we don't care if it's slightly for an overlap check.
stgt->UpdateIfDirtyIntersects(TranslateAlignedRectByPage(stgt, SBP, PSM, BW, GSVector4i(sx, sy, sx + w, sy + h), true));
// The main point of HW moves is so GPU data can get used as sources. If we don't flush all writes,
// we're not going to be able to use it as a source.
dtgt->Update();
// Need to offset based on the target's actual BP.
const u32 real_src_offset = ((SBP - stgt->m_TEX0.TBP0) / GS_BLOCKS_PER_PAGE) + src_page_offset;
const u32 real_dst_offset = ((DBP - dtgt->m_TEX0.TBP0) / GS_BLOCKS_PER_PAGE) + dst_page_offset;
+6
View File
@@ -1215,9 +1215,15 @@ std::string GameList::FormatTimestamp(std::time_t timestamp)
}
else
{
#ifdef _WIN32
wchar_t buf[128];
std::wcsftime(buf, std::size(buf), L"%x", &ttime);
ret = StringUtil::WideStringToUTF8String(buf);
#else
char buf[128];
std::strftime(buf, std::size(buf), "%x", &ttime);
ret.assign(buf);
#endif
}
}
+27 -2
View File
@@ -5,6 +5,7 @@
#include "CDVD/CDVDcommon.h"
#include "GS/Renderers/Common/GSDevice.h"
#include "GS/Renderers/Common/GSTexture.h"
#include "GS/GSCapture.h"
#include "Achievements.h"
#include "CDVD/CDVDdiscReader.h"
#include "GameList.h"
@@ -58,8 +59,15 @@ TinyString FullscreenUI::TimeToPrintableString(time_t t)
#endif
TinyString ret;
#ifdef _WIN32
wchar_t buf[65];
pxAssert(std::size(buf) == ret.buffer_size());
std::wcsftime(buf, std::size(buf), L"%c", &lt);
ret.assign(StringUtil::WideStringToUTF8String(buf));
#else
std::strftime(ret.data(), ret.buffer_size(), "%c", &lt);
ret.update_size();
#endif
return ret;
}
@@ -1309,9 +1317,16 @@ void FullscreenUI::DrawLandingTemplate(ImVec2* menu_pos, ImVec2* menu_size)
#else
localtime_r(&utc_time_t, &tm_local);
#endif
#ifdef _WIN32
wchar_t buf[256];
std::wcsftime(buf, std::size(buf), L"%X", &tm_local);
heading_str.assign(StringUtil::WideStringToUTF8String(buf));
#else
char buf[256];
std::strftime(buf, sizeof(buf), "%X", &tm_local);
std::strftime(buf, std::size(buf), "%X", &tm_local);
heading_str.assign(buf);
#endif
const ImVec2 time_size = heading_font.first->CalcTextSizeA(heading_font.second, FLT_MAX, 0.0f, heading_str.c_str());
time_pos = ImVec2(heading_size.x - LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING) - time_size.x,
@@ -1668,7 +1683,7 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type)
ImVec2(10.0f, 10.0f), ImGuiWindowFlags_NoBackground))
{
static constexpr u32 submenu_item_count[] = {
11, // None
12, // None
4, // Exit
3, // Achievements
};
@@ -1769,6 +1784,16 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type)
ClosePauseMenu();
}
const bool is_capturing = GSCapture::IsCapturing();
const bool can_start_capture = GSConfig.EnableVideoCapture || GSConfig.EnableAudioCapture;
if (ActiveButton(is_capturing ? FSUI_ICONSTR(ICON_FA_VIDEO_SLASH, "Stop Recording") :
FSUI_ICONSTR(ICON_FA_VIDEO, "Start Recording"),
false, is_capturing || can_start_capture))
{
GSToggleVideoCapture();
ClosePauseMenu();
}
if (ActiveButton(GSIsHardwareRenderer() ? (FSUI_ICONSTR(ICON_FA_PAINTBRUSH, "Switch To Software Renderer")) :
(FSUI_ICONSTR(ICON_FA_PAINTBRUSH, "Switch To Hardware Renderer")),
false))
+5
View File
@@ -426,6 +426,7 @@ namespace FullscreenUI
void SetSettingsChanged(SettingsInterface* bsi);
bool GetEffectiveBoolSetting(SettingsInterface* bsi, const char* section, const char* key, bool default_value);
s32 GetEffectiveIntSetting(SettingsInterface* bsi, const char* section, const char* key, s32 default_value);
std::string GetEffectiveStringSetting(SettingsInterface* bsi, const char* section, const char* key, const char* default_value);
void DoCopyGameSettings();
void DoClearGameSettings();
void ResetControllerSettings();
@@ -466,6 +467,10 @@ namespace FullscreenUI
void DrawStringListSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key,
const char* default_value, SettingInfo::GetOptionsCallback options_callback, bool enabled = true,
float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, std::pair<ImFont*, float> font = g_large_font, std::pair<ImFont*, float> summary_font = g_medium_font);
void DrawStringListSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key,
const char* default_value, const std::vector<std::pair<std::string, std::string>>& items, bool enabled = true,
std::vector<std::string> dependent_keys = {},
float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, std::pair<ImFont*, float> font = g_large_font, std::pair<ImFont*, float> summary_font = g_medium_font);
void DrawIPAddressSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key,
const char* default_value, bool enabled = true, float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT,
std::pair<ImFont*, float> font = g_large_font, std::pair<ImFont*, float> summary_font = g_medium_font,
+203 -8
View File
@@ -3,6 +3,7 @@
#include "GS/Renderers/Common/GSDevice.h"
#include "GS/Renderers/Common/GSTexture.h"
#include "GS/GSCapture.h"
#include "GS/GSUtil.h"
#include "Achievements.h"
#include "GameList.h"
@@ -240,6 +241,19 @@ s32 FullscreenUI::GetEffectiveIntSetting(SettingsInterface* bsi, const char* sec
return Host::Internal::GetBaseSettingsLayer()->GetIntValue(section, key, default_value);
}
std::string FullscreenUI::GetEffectiveStringSetting(
SettingsInterface* bsi, const char* section, const char* key, const char* default_value)
{
if (IsEditingGameSettings(bsi))
{
std::optional<std::string> value = bsi->GetOptionalStringValue(section, key, std::nullopt);
if (value.has_value())
return value.value();
}
return Host::Internal::GetBaseSettingsLayer()->GetStringValue(section, key, default_value);
}
void FullscreenUI::DrawInputBindingButton(
SettingsInterface* bsi, InputBindingInfo::Type type, const char* section, const char* name, const char* display_name, const char* icon_name, bool show_type)
{
@@ -1279,6 +1293,84 @@ void FullscreenUI::DrawStringListSetting(SettingsInterface* bsi, const char* tit
}
}
void FullscreenUI::DrawStringListSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section,
const char* key, const char* default_value, const std::vector<std::pair<std::string, std::string>>& items, bool enabled,
std::vector<std::string> dependent_keys, float height, std::pair<ImFont*, float> font, std::pair<ImFont*, float> summary_font)
{
const bool game_settings = IsEditingGameSettings(bsi);
const std::optional<SmallString> value(
bsi->GetOptionalSmallStringValue(section, key, (game_settings || !default_value) ? std::nullopt : std::optional<const char*>(default_value)));
const char* display_value = value.has_value() ? FSUI_CSTR("Unknown") : FSUI_CSTR("Use Global Setting");
size_t current_index = items.size();
if (value.has_value())
{
for (size_t i = 0; i < items.size(); i++)
{
if (value.value() == items[i].first)
{
current_index = i;
display_value = items[i].second.c_str();
break;
}
}
}
if (MenuButtonWithValue(title, summary, display_value, enabled, height, font, summary_font))
{
std::vector<std::string> option_values;
option_values.reserve(items.size());
ImGuiFullscreen::ChoiceDialogOptions cd_options;
cd_options.reserve(items.size() + 1);
if (game_settings)
cd_options.emplace_back(FSUI_STR("Use Global Setting"), !value.has_value());
for (size_t i = 0; i < items.size(); i++)
{
option_values.push_back(items[i].first);
cd_options.emplace_back(items[i].second, (value.has_value() && i == current_index));
}
OpenChoiceDialog(title, false, std::move(cd_options),
[game_settings, section = std::string(section), key = std::string(key),
dependent_keys = std::move(dependent_keys),
default_value = default_value ? std::string(default_value) : std::string(),
option_values = std::move(option_values)](s32 index, const std::string& title, bool checked) {
if (index >= 0)
{
auto lock = Host::GetSettingsLock();
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
const std::string old_value = GetEffectiveStringSetting(bsi, section.c_str(), key.c_str(), default_value.c_str());
const std::optional<std::string> new_value = (game_settings && index == 0) ?
std::nullopt :
std::optional<std::string>(option_values[index - (game_settings ? 1 : 0)]);
if (new_value.has_value())
bsi->SetStringValue(section.c_str(), key.c_str(), new_value->c_str());
else
bsi->DeleteValue(section.c_str(), key.c_str());
if (!dependent_keys.empty() && old_value != GetEffectiveStringSetting(bsi, section.c_str(), key.c_str(), default_value.c_str()))
{
for (const std::string& dep_key : dependent_keys)
{
if (!dep_key.empty())
{
bsi->SetStringValue(section.c_str(), dep_key.c_str(), "");
}
}
}
SetSettingsChanged(bsi);
}
CloseChoiceDialog();
});
}
}
void FullscreenUI::DrawFloatListSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section,
const char* key, float default_value, const char* const* options, const float* option_values, size_t option_count,
bool translate_options, bool enabled, float height, std::pair<ImFont*, float> font, std::pair<ImFont*, float> summary_font)
@@ -3034,14 +3126,6 @@ void FullscreenUI::DrawGraphicsSettingsPage(SettingsInterface* bsi, bool show_ad
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_TV, "Disable Interlace Offset"),
FSUI_CSTR("Disables interlacing offset which may reduce blurring in some situations."), "EmuCore/GS",
"disable_interlace_offset", false);
DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_FA_ARROWS_UP_DOWN_LEFT_RIGHT, "Screenshot Resolution"), FSUI_CSTR("Determines the resolution at which screenshots will be saved."),
"EmuCore/GS", "ScreenshotSize", static_cast<int>(GSScreenshotSize::WindowResolution), s_screenshot_sizes,
std::size(s_screenshot_sizes), true);
DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_FA_PHOTO_FILM, "Screenshot Format"), FSUI_CSTR("Selects the format which will be used to save screenshots."),
"EmuCore/GS", "ScreenshotFormat", static_cast<int>(GSScreenshotFormat::PNG), s_screenshot_formats, std::size(s_screenshot_formats),
true);
DrawIntRangeSetting(bsi, FSUI_ICONSTR(ICON_FA_GAUGE, "Screenshot Quality"), FSUI_CSTR("Selects the quality at which screenshots will be compressed."),
"EmuCore/GS", "ScreenshotQuality", 90, 1, 100, FSUI_CSTR("%d%%"));
DrawIntRangeSetting(bsi, FSUI_ICONSTR(ICON_FA_ARROW_RIGHT_ARROW_LEFT, "Vertical Stretch"), FSUI_CSTR("Increases or decreases the virtual picture size vertically."),
"EmuCore/GS", "StretchY", 100, 10, 300, FSUI_CSTR("%d%%"));
DrawIntRectSetting(bsi, FSUI_ICONSTR(ICON_FA_CROP, "Crop"), FSUI_CSTR("Crops the image, while respecting aspect ratio."), "EmuCore/GS", "CropLeft", 0,
@@ -3362,6 +3446,117 @@ void FullscreenUI::DrawGraphicsSettingsPage(SettingsInterface* bsi, bool show_ad
s_tv_shaders, std::size(s_tv_shaders), true);
}
MenuHeading(FSUI_CSTR("Media Capture"));
DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_FA_ARROWS_UP_DOWN_LEFT_RIGHT, "Screenshot Resolution"), FSUI_CSTR("Determines the resolution at which screenshots will be saved."),
"EmuCore/GS", "ScreenshotSize", static_cast<int>(GSScreenshotSize::WindowResolution), s_screenshot_sizes,
std::size(s_screenshot_sizes), true);
DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_FA_PHOTO_FILM, "Screenshot Format"), FSUI_CSTR("Selects the format which will be used to save screenshots."),
"EmuCore/GS", "ScreenshotFormat", static_cast<int>(GSScreenshotFormat::PNG), s_screenshot_formats, std::size(s_screenshot_formats),
true);
DrawIntRangeSetting(bsi, FSUI_ICONSTR(ICON_FA_GAUGE, "Screenshot Quality"), FSUI_CSTR("Selects the quality at which screenshots will be compressed."),
"EmuCore/GS", "ScreenshotQuality", 90, 1, 100, FSUI_CSTR("%d%%"));
static const std::vector<std::pair<std::string, std::string>> s_capture_container_options = []() {
std::vector<std::pair<std::string, std::string>> options;
for (const char** container = Pcsx2Config::GSOptions::CaptureContainers; *container; container++)
options.emplace_back(*container, StringUtil::toUpper(*container));
return options;
}();
DrawStringListSetting(bsi, FSUI_ICONSTR(ICON_FA_BOX_ARCHIVE, "Container Format"),
FSUI_CSTR("Selects the media container file format for recordings."), "EmuCore/GS", "CaptureContainer",
Pcsx2Config::GSOptions::DEFAULT_CAPTURE_CONTAINER, s_capture_container_options, true,
{"VideoCaptureCodec", "AudioCaptureCodec", "VideoCaptureFormat"});
const bool enable_video_capture = GetEffectiveBoolSetting(bsi, "EmuCore/GS", "EnableVideoCapture", true);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_VIDEO, "Capture Video"),
FSUI_CSTR("Includes video in recordings."), "EmuCore/GS", "EnableVideoCapture", true);
const std::string container = GetEffectiveStringSetting(
bsi, "EmuCore/GS", "CaptureContainer", Pcsx2Config::GSOptions::DEFAULT_CAPTURE_CONTAINER);
static std::string s_last_capture_container;
static std::vector<std::pair<std::string, std::string>> s_video_codec_list_cache;
static std::vector<std::pair<std::string, std::string>> s_audio_codec_list_cache;
static bool s_capture_lists_initialized = false;
if (!s_capture_lists_initialized || s_last_capture_container != container)
{
s_last_capture_container = container;
s_capture_lists_initialized = true;
s_video_codec_list_cache.clear();
s_video_codec_list_cache.emplace_back("", FSUI_STR("Default"));
for (const auto& codec : GSCapture::GetVideoCodecList(container.c_str()))
s_video_codec_list_cache.emplace_back(codec.first, codec.first);
s_audio_codec_list_cache.clear();
s_audio_codec_list_cache.emplace_back("", FSUI_STR("Default"));
for (const auto& codec : GSCapture::GetAudioCodecList(container.c_str()))
s_audio_codec_list_cache.emplace_back(codec.first, codec.first);
}
DrawStringListSetting(bsi, FSUI_ICONSTR(ICON_FA_FILM, "Video Codec"),
FSUI_CSTR("Selects the video codec used for recordings. If unsure, leave this set to Default."),
"EmuCore/GS", "VideoCaptureCodec", "", s_video_codec_list_cache, enable_video_capture, {"VideoCaptureFormat"});
const std::string codec = GetEffectiveStringSetting(bsi, "EmuCore/GS", "VideoCaptureCodec", "");
static std::string s_last_capture_codec;
static std::vector<std::pair<std::string, std::string>> s_video_format_list_cache;
static bool s_format_list_initialized = false;
if (!s_format_list_initialized || s_last_capture_codec != codec)
{
s_last_capture_codec = codec;
s_format_list_initialized = true;
s_video_format_list_cache.clear();
s_video_format_list_cache.emplace_back("", FSUI_STR("Default"));
if (!codec.empty())
{
for (const auto& [id, name] : GSCapture::GetVideoFormatList(codec.c_str()))
s_video_format_list_cache.emplace_back(fmt::to_string(id), name);
}
}
DrawStringListSetting(bsi, FSUI_ICONSTR(ICON_FA_IMAGE, "Video Format"),
FSUI_CSTR("Selects the pixel format used for recordings. Unsupported formats fall back to a format supported by the codec."),
"EmuCore/GS", "VideoCaptureFormat", "", s_video_format_list_cache, enable_video_capture);
DrawIntSpinBoxSetting(bsi, FSUI_ICONSTR(ICON_FA_GAUGE, "Video Bitrate"),
FSUI_CSTR("Sets the video bitrate. Higher bitrates generally improve quality but increase file size."),
"EmuCore/GS", "VideoCaptureBitrate", Pcsx2Config::GSOptions::DEFAULT_VIDEO_CAPTURE_BITRATE, 100, 200000, 500,
FSUI_CSTR("%d kbps"), enable_video_capture);
const bool video_auto_resolution = GetEffectiveBoolSetting(bsi, "EmuCore/GS", "VideoCaptureAutoResolution", true);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_ARROWS_UP_DOWN_LEFT_RIGHT, "Automatic Resolution"),
FSUI_CSTR("When checked, the video capture resolution will follow the internal resolution of the running game."),
"EmuCore/GS", "VideoCaptureAutoResolution", true, enable_video_capture);
DrawIntSpinBoxSetting(bsi, FSUI_ICONSTR(ICON_FA_ARROWS_LEFT_RIGHT, "Video Capture Width"),
FSUI_CSTR("Sets the recording width when Automatic Resolution is disabled."),
"EmuCore/GS", "VideoCaptureWidth", Pcsx2Config::GSOptions::DEFAULT_VIDEO_CAPTURE_WIDTH, 320, 32768, 16,
FSUI_CSTR("%dpx"), enable_video_capture && !video_auto_resolution);
DrawIntSpinBoxSetting(bsi, FSUI_ICONSTR(ICON_FA_ARROWS_UP_DOWN, "Video Capture Height"),
FSUI_CSTR("Sets the recording height when Automatic Resolution is disabled."),
"EmuCore/GS", "VideoCaptureHeight", Pcsx2Config::GSOptions::DEFAULT_VIDEO_CAPTURE_HEIGHT, 240, 32768, 16,
FSUI_CSTR("%dpx"), enable_video_capture && !video_auto_resolution);
const bool enable_audio_capture = GetEffectiveBoolSetting(bsi, "EmuCore/GS", "EnableAudioCapture", true);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_VOLUME_HIGH, "Capture Audio"),
FSUI_CSTR("Includes audio in recordings."), "EmuCore/GS", "EnableAudioCapture", true);
DrawStringListSetting(bsi, FSUI_ICONSTR(ICON_FA_HEADPHONES, "Audio Codec"),
FSUI_CSTR("Selects the audio codec used for recordings. If unsure, leave this set to Default."),
"EmuCore/GS", "AudioCaptureCodec", "", s_audio_codec_list_cache, enable_audio_capture);
DrawIntSpinBoxSetting(bsi, FSUI_ICONSTR(ICON_FA_GAUGE, "Audio Bitrate"),
FSUI_CSTR("Sets the audio bitrate."),
"EmuCore/GS", "AudioCaptureBitrate", Pcsx2Config::GSOptions::DEFAULT_AUDIO_CAPTURE_BITRATE, 16, 2048, 16,
FSUI_CSTR("%d kbps"), enable_audio_capture);
static constexpr const char* s_gsdump_compression[] = {
FSUI_NSTR("Uncompressed"),
FSUI_NSTR("LZMA (xz)"),
+2
View File
@@ -123,8 +123,10 @@ void Pad::LoadConfig(const SettingsInterface& si)
const float axis_deadzone = si.GetFloatValue(section.c_str(), "Deadzone", Pad::DEFAULT_STICK_DEADZONE);
const float axis_scale = si.GetFloatValue(section.c_str(), "AxisScale", Pad::DEFAULT_STICK_SCALE);
const bool use_diagonal_scale_correction = si.GetBoolValue(section.c_str(), "UseDiagonalScaleCorrection", Pad::DEFAULT_USE_DIAGONAL_SCALE_CORRECTION);
const float button_deadzone = si.GetFloatValue(section.c_str(), "ButtonDeadzone", Pad::DEFAULT_BUTTON_DEADZONE);
pad->SetAxisScale(axis_deadzone, axis_scale);
pad->SetDiagonalScaleCorrection(use_diagonal_scale_correction);
pad->SetButtonDeadzone(button_deadzone);
if (ci->vibration_caps != Pad::VibrationCapabilities::NoVibration)
+1
View File
@@ -43,6 +43,7 @@ public: // Public members
virtual void SetRawAnalogs(const std::tuple<u8, u8> left, const std::tuple<u8, u8> right) = 0;
virtual void SetRawPressureButton(u32 index, const std::tuple<bool, u8> value) = 0;
virtual void SetAxisScale(float deadzone, float scale) = 0;
virtual void SetDiagonalScaleCorrection(bool enabled) = 0;
virtual float GetVibrationScale(u32 motor) const = 0;
virtual void SetVibrationScale(u32 motor, float scale) = 0;
virtual float GetPressureModifier() const = 0;
+53 -6
View File
@@ -12,6 +12,9 @@
#include "IconsPromptFont.h"
#include <algorithm>
#include <cmath>
static const InputBindingInfo s_bindings[] = {
// clang-format off
{"Up", TRANSLATE_NOOP("Pad", "D-Pad Up"), ICON_PF_DPAD_UP, InputBindingInfo::Type::Button, PadDualshock2::Inputs::PAD_UP, GenericInputBinding::DPadUp},
@@ -62,9 +65,14 @@ static const SettingInfo s_settings[] = {
"0.00", "0.00", "1.00", "0.01", TRANSLATE_NOOP("Pad", "%.0f%%"), nullptr, nullptr, 100.0f},
{SettingInfo::Type::Float, "AxisScale", TRANSLATE_NOOP("Pad", "Analog Sensitivity"),
TRANSLATE_NOOP("Pad",
"Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent "
"controllers, e.g. DualShock 4, Xbox One Controller."),
"Sets the analog stick axis scaling factor. Modern controllers with square pickup zones, e.g. DualShock 4, Xbox One "
"have better diagonal inputs at a range between 130-140%, but this will cause axes to reach max input early."),
"1.33", "0.01", "2.00", "0.01", TRANSLATE_NOOP("Pad", "%.0f%%"), nullptr, nullptr, 100.0f},
{SettingInfo::Type::Boolean, "UseDiagonalScaleCorrection", TRANSLATE_NOOP("Pad", "Use Diagonal Scale Correction"),
TRANSLATE_NOOP("Pad",
"Transforms analog stick pickup zone from a square to a circle, allows for full input at diagonals without also scaling the X and Y axes. "
"Ideal for modern controllers with square pickup zones, e.g. DualShock 4, Xbox One. Setting Analog Sensitivity to 100% is recommended with this setting."),
"false", "", "", "", TRANSLATE_NOOP("Pad", "%.0f%%"), nullptr, nullptr, 0.0f},
{SettingInfo::Type::Float, "LargeMotorScale", TRANSLATE_NOOP("Pad", "Large Motor Vibration Scale"),
TRANSLATE_NOOP("Pad", "Increases or decreases the intensity of low frequency vibration sent by the game."),
"1.00", "0.00", "2.00", "0.01", TRANSLATE_NOOP("Pad", "%.0f%%"), nullptr, nullptr, 100.0f},
@@ -576,14 +584,48 @@ void PadDualshock2::Set(u32 index, float value)
if (index <= Inputs::PAD_L_LEFT)
{
// Left Stick
this->analogs.lx = this->analogs.lxInvert ? MERGE(Inputs::PAD_L_LEFT, Inputs::PAD_L_RIGHT) : MERGE(Inputs::PAD_L_RIGHT, Inputs::PAD_L_LEFT);
this->analogs.ly = this->analogs.lyInvert ? MERGE(Inputs::PAD_L_UP, Inputs::PAD_L_DOWN) : MERGE(Inputs::PAD_L_DOWN, Inputs::PAD_L_UP);
const u8 combinedX = this->analogs.lxInvert ? MERGE(Inputs::PAD_L_LEFT, Inputs::PAD_L_RIGHT) : MERGE(Inputs::PAD_L_RIGHT, Inputs::PAD_L_LEFT);
const u8 combinedY = this->analogs.lyInvert ? MERGE(Inputs::PAD_L_UP, Inputs::PAD_L_DOWN) : MERGE(Inputs::PAD_L_DOWN, Inputs::PAD_L_UP);
if (this->useDiagonalScaleCorrection)
{
const float normalizedX = (combinedX - 127.5) / 127.5f;
const float normalizedY = (combinedY - 127.5) / 127.5f;
const float magnitude = std::sqrt((normalizedX * normalizedX) + (normalizedY * normalizedY));
const float max = std::max(std::abs(normalizedX), std::abs(normalizedY));
const float scaledX = (normalizedX / max) * std::min(magnitude, 1.0f);
const float scaledY = (normalizedY / max) * std::min(magnitude, 1.0f);
this->analogs.lx = (scaledX * 127.5) + 127.5;
this->analogs.ly = (scaledY * 127.5) + 127.5;
}
else
{
this->analogs.lx = combinedX;
this->analogs.ly = combinedY;
}
}
else
{
// Right Stick
this->analogs.rx = this->analogs.rxInvert ? MERGE(Inputs::PAD_R_LEFT, Inputs::PAD_R_RIGHT) : MERGE(Inputs::PAD_R_RIGHT, Inputs::PAD_R_LEFT);
this->analogs.ry = this->analogs.ryInvert ? MERGE(Inputs::PAD_R_UP, Inputs::PAD_R_DOWN) : MERGE(Inputs::PAD_R_DOWN, Inputs::PAD_R_UP);
const u8 combinedX = this->analogs.rxInvert ? MERGE(Inputs::PAD_R_LEFT, Inputs::PAD_R_RIGHT) : MERGE(Inputs::PAD_R_RIGHT, Inputs::PAD_R_LEFT);
const u8 combinedY = this->analogs.ryInvert ? MERGE(Inputs::PAD_R_UP, Inputs::PAD_R_DOWN) : MERGE(Inputs::PAD_R_DOWN, Inputs::PAD_R_UP);
if (this->useDiagonalScaleCorrection)
{
const float normalizedX = (combinedX - 127.5) / 127.5f;
const float normalizedY = (combinedY - 127.5) / 127.5f;
const float magnitude = std::sqrt((normalizedX * normalizedX) + (normalizedY * normalizedY));
const float max = std::max(std::abs(normalizedX), std::abs(normalizedY));
const float scaledX = (normalizedX / max) * std::min(magnitude, 1.0f);
const float scaledY = (normalizedY / max) * std::min(magnitude, 1.0f);
this->analogs.rx = (scaledX * 127.5) + 127.5;
this->analogs.ry = (scaledY * 127.5) + 127.5;
}
else
{
this->analogs.rx = combinedX;
this->analogs.ry = combinedY;
}
}
#undef MERGE
@@ -739,6 +781,11 @@ void PadDualshock2::SetAxisScale(float deadzone, float scale)
this->axisScale = scale;
}
void PadDualshock2::SetDiagonalScaleCorrection(bool enabled)
{
this->useDiagonalScaleCorrection = enabled;
}
float PadDualshock2::GetVibrationScale(u32 motor) const
{
return this->vibrationScale[motor];
+3
View File
@@ -72,6 +72,8 @@ private:
std::array<u8, VIBRATION_MOTORS> vibrationMotors = {};
float axisScale = 1.0f;
float axisDeadzone = 0.0f;
// Determines if inputs from the host should be corrected from square pickup zone to circular
bool useDiagonalScaleCorrection = false;
std::array<float, 2> vibrationScale = {1.0f, 1.0f};
// When the pressure modifier binding is activated, this is multiplied against
// all values in pressures, to artificially reduce pressures and give players
@@ -140,6 +142,7 @@ public:
void SetRawAnalogs(const std::tuple<u8, u8> left, const std::tuple<u8, u8> right) override;
void SetRawPressureButton(u32 index, const std::tuple<bool, u8> value) override;
void SetAxisScale(float deadzone, float scale) override;
void SetDiagonalScaleCorrection(bool enabled) override;
float GetVibrationScale(u32 motor) const override;
void SetVibrationScale(u32 motor, float scale) override;
float GetPressureModifier() const override;
+4
View File
@@ -338,6 +338,10 @@ void PadGuitar::SetAxisScale(float deadzone, float scale)
this->whammyAxisScale = scale;
}
void PadGuitar::SetDiagonalScaleCorrection(bool enabled)
{
}
float PadGuitar::GetVibrationScale(u32 motor) const
{
return 0;
+1
View File
@@ -69,6 +69,7 @@ public:
void SetRawAnalogs(const std::tuple<u8, u8> left, const std::tuple<u8, u8> right) override;
void SetRawPressureButton(u32 index, const std::tuple<bool, u8> value) override;
void SetAxisScale(float deadzone, float scale) override;
void SetDiagonalScaleCorrection(bool enabled) override;
float GetVibrationScale(u32 motor) const override;
void SetVibrationScale(u32 motor, float scale) override;
float GetPressureModifier() const override;
+4
View File
@@ -376,6 +376,10 @@ void PadJogcon::SetAxisScale(float deadzone, float scale)
this->dialScale = scale;
}
void PadJogcon::SetDiagonalScaleCorrection(bool enabled)
{
}
float PadJogcon::GetVibrationScale(u32 motor) const
{
return this->vibrationScale[motor];
+1
View File
@@ -99,6 +99,7 @@ public:
void SetRawAnalogs(const std::tuple<u8, u8> left, const std::tuple<u8, u8> right) override;
void SetRawPressureButton(u32 index, const std::tuple<bool, u8> value) override;
void SetAxisScale(float deadzone, float scale) override;
void SetDiagonalScaleCorrection(bool enabled) override;
float GetVibrationScale(u32 motor) const override;
void SetVibrationScale(u32 motor, float scale) override;
float GetPressureModifier() const override;
+4
View File
@@ -375,6 +375,10 @@ void PadNegcon::SetAxisScale(float deadzone, float scale)
this->twistScale = scale;
}
void PadNegcon::SetDiagonalScaleCorrection(bool enabled)
{
}
float PadNegcon::GetVibrationScale(u32 motor) const
{
return this->vibrationScale[motor];
+1
View File
@@ -111,6 +111,7 @@ public:
void SetRawAnalogs(const std::tuple<u8, u8> left, const std::tuple<u8, u8> right) override;
void SetRawPressureButton(u32 index, const std::tuple<bool, u8> value) override;
void SetAxisScale(float deadzone, float scale) override;
void SetDiagonalScaleCorrection(bool enabled) override;
float GetVibrationScale(u32 motor) const override;
void SetVibrationScale(u32 motor, float scale) override;
float GetPressureModifier() const override;
+5
View File
@@ -46,6 +46,11 @@ void PadNotConnected::SetAxisScale(float deadzone, float scale)
}
void PadNotConnected::SetDiagonalScaleCorrection(bool enabled)
{
}
float PadNotConnected::GetVibrationScale(u32 motor) const
{
return 0;
+1
View File
@@ -17,6 +17,7 @@ public:
void SetRawAnalogs(const std::tuple<u8, u8> left, const std::tuple<u8, u8> right) override;
void SetRawPressureButton(u32 index, const std::tuple<bool, u8> value) override;
void SetAxisScale(float deadzone, float scale) override;
void SetDiagonalScaleCorrection(bool enabled) override;
float GetVibrationScale(u32 motor) const override;
void SetVibrationScale(u32 motor, float scale) override;
float GetPressureModifier() const override;
+4
View File
@@ -410,6 +410,10 @@ void PadPopn::SetAxisScale(float deadzone, float scale)
{
}
void PadPopn::SetDiagonalScaleCorrection(bool enabled)
{
}
float PadPopn::GetVibrationScale(u32 motor) const
{
return 0;
+1
View File
@@ -96,6 +96,7 @@ public:
void SetRawAnalogs(const std::tuple<u8, u8> left, const std::tuple<u8, u8> right) override;
void SetRawPressureButton(u32 index, const std::tuple<bool, u8> value) override;
void SetAxisScale(float deadzone, float scale) override;
void SetDiagonalScaleCorrection(bool enabled) override;
float GetVibrationScale(u32 motor) const override;
void SetVibrationScale(u32 motor, float scale) override;
float GetPressureModifier() const override;
+1
View File
@@ -101,6 +101,7 @@ namespace Pad
// Default stick deadzone/sensitivity.
static constexpr float DEFAULT_STICK_DEADZONE = 0.0f;
static constexpr float DEFAULT_STICK_SCALE = 1.33f;
static constexpr float DEFAULT_USE_DIAGONAL_SCALE_CORRECTION = false;
static constexpr float DEFAULT_MOTOR_SCALE = 1.0f;
static constexpr float DEFAULT_PRESSURE_MODIFIER = 0.5f;
static constexpr float DEFAULT_BUTTON_DEADZONE = 0.0f;