mirror of https://github.com/PCSX2/pcsx2.git
GS: Add internal resolution screenshot option
This commit is contained in:
parent
a67d3e9aee
commit
354951f1d6
|
@ -112,6 +112,9 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
|
|||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.PCRTCOverscan, "EmuCore/GS", "pcrtc_overscan", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.PCRTCAntiBlur, "EmuCore/GS", "pcrtc_antiblur", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.DisableInterlaceOffset, "EmuCore/GS", "disable_interlace_offset", false);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.screenshotSize, "EmuCore/GS", "ScreenshotSize", static_cast<int>(GSScreenshotSize::WindowResolution));
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.screenshotFormat, "EmuCore/GS", "ScreenshotFormat", static_cast<int>(GSScreenshotFormat::PNG));
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.screenshotQuality, "EmuCore/GS", "ScreenshotQuality", 50);
|
||||
SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.stretchY, "EmuCore/GS", "StretchY", 100.0f);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.cropLeft, "EmuCore/GS", "CropLeft", 0);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.cropTop, "EmuCore/GS", "CropTop", 0);
|
||||
|
@ -356,6 +359,13 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
|
|||
|
||||
dialog->registerWidgetHelp(m_ui.interlacing, tr("Deinterlacing"), tr("Automatic (Default)"), tr(""));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.screenshotSize, tr("Screenshot Size"), tr("Screen Resolution"),
|
||||
tr("Determines the resolution at which screenshots will be saved. Internal resolutions preserve more detail at the cost of file size."));
|
||||
dialog->registerWidgetHelp(m_ui.screenshotFormat, tr("Screenshot Format"), tr("PNG"),
|
||||
tr("Selects the format which will be used to save screenshots. JPEG produces smaller files, but loses detail."));
|
||||
dialog->registerWidgetHelp(m_ui.screenshotQuality, tr("Screenshot Quality"), tr("50%"),
|
||||
tr("Selects the quality at which screenshots will be compressed. Higher values preserve more detail for JPEG, and reduce file size for PNG."));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.stretchY, tr("Stretch Height"), tr("100%"), tr(""));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.fullscreenModes, tr("Fullscreen Mode"), tr("Borderless Fullscreen"), tr(""));
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>850</width>
|
||||
<height>500</height>
|
||||
<width>861</width>
|
||||
<height>501</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -227,14 +227,14 @@
|
|||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_24">
|
||||
<property name="text">
|
||||
<string>Vertical Stretch:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<item row="6" column="1">
|
||||
<widget class="QSpinBox" name="stretchY">
|
||||
<property name="suffix">
|
||||
<string>%</string>
|
||||
|
@ -247,14 +247,14 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_26">
|
||||
<property name="text">
|
||||
<string>Crop:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<item row="7" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="0,1,0,1,0,1,0,1">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_39">
|
||||
|
@ -326,7 +326,7 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<item row="8" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="PCRTCOffsets">
|
||||
|
@ -389,6 +389,70 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_21">
|
||||
<property name="text">
|
||||
<string>Screenshot Size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0,0,0">
|
||||
<item>
|
||||
<widget class="QComboBox" name="screenshotSize">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Screen Resolution</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Internal Resolution</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Internal Resolution (Aspect Uncorrected)</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="screenshotFormat">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>PNG</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>JPEG</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_44">
|
||||
<property name="text">
|
||||
<string>Quality:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="screenshotQuality">
|
||||
<property name="suffix">
|
||||
<string>%</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QGroupBox" name="hardwareRenderingTab">
|
||||
|
|
|
@ -204,6 +204,20 @@ enum class TexturePreloadingLevel : u8
|
|||
Full,
|
||||
};
|
||||
|
||||
enum class GSScreenshotSize : u8
|
||||
{
|
||||
WindowResolution,
|
||||
InternalResolution,
|
||||
InternalResolutionUncorrected,
|
||||
};
|
||||
|
||||
enum class GSScreenshotFormat : u8
|
||||
{
|
||||
PNG,
|
||||
JPEG,
|
||||
Count,
|
||||
};
|
||||
|
||||
enum class GSDumpCompressionMethod : u8
|
||||
{
|
||||
Uncompressed,
|
||||
|
@ -606,8 +620,14 @@ struct Pcsx2Config
|
|||
int ShadeBoost_Brightness{50};
|
||||
int ShadeBoost_Contrast{50};
|
||||
int ShadeBoost_Saturation{50};
|
||||
|
||||
int SaveN{0};
|
||||
int SaveL{5000};
|
||||
|
||||
GSScreenshotSize ScreenshotSize{GSScreenshotSize::WindowResolution};
|
||||
GSScreenshotFormat ScreenshotFormat{GSScreenshotFormat::PNG};
|
||||
int ScreenshotQuality{50};
|
||||
|
||||
std::string Adapter;
|
||||
|
||||
GSOptions();
|
||||
|
|
|
@ -2812,6 +2812,8 @@ void FullscreenUI::DrawGraphicsSettingsPage()
|
|||
static constexpr const char* s_generic_options[] = {"Automatic (Default)", "Force Disabled", "Force Enabled"};
|
||||
static constexpr const char* s_hw_download[] = {"Accurate (Recommended)", "Disable Readbacks (Synchronize GS Thread)",
|
||||
"Unsynchronized (Non-Deterministic)", "Disabled (Ignore Transfers)"};
|
||||
static constexpr const char* s_screenshot_sizes[] = {"Screen Resolution", "Internal Resolution", "Internal Resolution (Uncorrected)"};
|
||||
static constexpr const char* s_screenshot_formats[] = {"PNG", "JPEG"};
|
||||
|
||||
SettingsInterface* bsi = GetEditingSettingsInterface();
|
||||
|
||||
|
@ -2838,6 +2840,12 @@ void FullscreenUI::DrawGraphicsSettingsPage()
|
|||
DrawIntListSetting(bsi, "Deinterlacing",
|
||||
"Selects the algorithm used to convert the PS2's interlaced output to progressive for display.", "EmuCore/GS", "deinterlace_mode",
|
||||
static_cast<int>(GSInterlaceMode::Automatic), s_deinterlacing_options, std::size(s_deinterlacing_options));
|
||||
DrawIntListSetting(bsi, "Screenshot Size", "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));
|
||||
DrawIntListSetting(bsi, "Screenshot Format", "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));
|
||||
DrawIntRangeSetting(bsi, "Screenshot Quality", "Selects the quality at which screenshots will be compressed.", "EmuCore/GS",
|
||||
"ScreenshotQuality", 50, 1, 100, "%d%%");
|
||||
DrawIntRangeSetting(bsi, "Vertical Stretch", "Increases or decreases the virtual picture size vertically.", "EmuCore/GS", "StretchY",
|
||||
100, 10, 300, "%d%%");
|
||||
DrawIntRectSetting(bsi, "Crop", "Crops the image, while respecting aspect ratio.", "EmuCore/GS", "CropLeft", 0, "CropTop", 0,
|
||||
|
|
|
@ -417,7 +417,8 @@ public:
|
|||
void SwitchRenderer(GSRendererType renderer, bool display_message = true);
|
||||
void SetSoftwareRendering(bool software, bool display_message = true);
|
||||
void ToggleSoftwareRendering();
|
||||
bool SaveMemorySnapshot(u32 width, u32 height, std::vector<u32>* pixels);
|
||||
bool SaveMemorySnapshot(u32 window_width, u32 window_height, bool apply_aspect, bool crop_borders,
|
||||
u32* width, u32* height, std::vector<u32>* pixels);
|
||||
void SetRunIdle(bool enabled);
|
||||
|
||||
protected:
|
||||
|
|
|
@ -919,12 +919,14 @@ void GSRestoreAPIState()
|
|||
g_gs_device->RestoreAPIState();
|
||||
}
|
||||
|
||||
bool GSSaveSnapshotToMemory(u32 width, u32 height, std::vector<u32>* pixels)
|
||||
bool GSSaveSnapshotToMemory(u32 window_width, u32 window_height, bool apply_aspect, bool crop_borders,
|
||||
u32* width, u32* height, std::vector<u32>* pixels)
|
||||
{
|
||||
if (!g_gs_renderer)
|
||||
return false;
|
||||
|
||||
return g_gs_renderer->SaveSnapshotToMemory(width, height, pixels);
|
||||
return g_gs_renderer->SaveSnapshotToMemory(window_width, window_height, apply_aspect, crop_borders,
|
||||
width, height, pixels);
|
||||
}
|
||||
|
||||
std::string format(const char* fmt, ...)
|
||||
|
|
|
@ -91,7 +91,8 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config);
|
|||
void GSSwitchRenderer(GSRendererType new_renderer);
|
||||
void GSResetAPIState();
|
||||
void GSRestoreAPIState();
|
||||
bool GSSaveSnapshotToMemory(u32 width, u32 height, std::vector<u32>* pixels);
|
||||
bool GSSaveSnapshotToMemory(u32 window_width, u32 window_height, bool apply_aspect, bool crop_borders,
|
||||
u32* width, u32* height, std::vector<u32>* pixels);
|
||||
|
||||
class GSApp
|
||||
{
|
||||
|
|
|
@ -309,12 +309,6 @@ void GSDevice::ClearCurrent()
|
|||
m_mad = nullptr;
|
||||
m_target_tmp = nullptr;
|
||||
m_cas = nullptr;
|
||||
m_temp_snapshot = nullptr;
|
||||
}
|
||||
|
||||
void GSDevice::SetSnapshot()
|
||||
{
|
||||
m_temp_snapshot = m_current;
|
||||
}
|
||||
|
||||
void GSDevice::Merge(GSTexture* sTex[3], GSVector4* sRect, GSVector4* dRect, const GSVector2i& fs, const GSRegPMODE& PMODE, const GSRegEXTBUF& EXTBUF, const GSVector4& c)
|
||||
|
|
|
@ -740,7 +740,6 @@ protected:
|
|||
GSTexture* m_target_tmp = nullptr;
|
||||
GSTexture* m_current = nullptr;
|
||||
GSTexture* m_cas = nullptr;
|
||||
GSTexture* m_temp_snapshot = nullptr; // No need to delete this, only ever points to m_current.
|
||||
|
||||
struct
|
||||
{
|
||||
|
@ -841,10 +840,8 @@ public:
|
|||
|
||||
__fi FeatureSupport Features() const { return m_features; }
|
||||
__fi GSTexture* GetCurrent() const { return m_current; }
|
||||
__fi GSTexture* GetSnapshot() const { return m_temp_snapshot; }
|
||||
|
||||
void ClearCurrent();
|
||||
void SetSnapshot();
|
||||
void Merge(GSTexture* sTex[3], GSVector4* sRect, GSVector4* dRect, const GSVector2i& fs, const GSRegPMODE& PMODE, const GSRegEXTBUF& EXTBUF, const GSVector4& c);
|
||||
void Interlace(const GSVector2i& ds, int field, int mode, float yoffset);
|
||||
void FXAA();
|
||||
|
|
|
@ -20,7 +20,9 @@
|
|||
#include "HostDisplay.h"
|
||||
#include "PerformanceMetrics.h"
|
||||
#include "pcsx2/Config.h"
|
||||
#include "IconsFontAwesome5.h"
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/Image.h"
|
||||
#include "common/Path.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "common/Timer.h"
|
||||
|
@ -398,8 +400,6 @@ bool GSRenderer::Merge(int field)
|
|||
if (GSConfig.FXAA)
|
||||
g_gs_device->FXAA();
|
||||
|
||||
g_gs_device->SetSnapshot();
|
||||
|
||||
// Sharpens biinear at lower resolutions, almost nearest but with more uniform pixels.
|
||||
if (GSConfig.LinearPresent == GSPostBilinearMode::BilinearSharp && (g_host_display->GetWindowWidth() > fs.x || g_host_display->GetWindowHeight() > fs.y))
|
||||
{
|
||||
|
@ -533,9 +533,6 @@ static GSVector4 CalculateDrawDstRect(s32 window_width, s32 window_height, const
|
|||
|
||||
static GSVector4i CalculateDrawSrcRect(const GSTexture* src)
|
||||
{
|
||||
#ifndef PCSX2_CORE
|
||||
return GSVector4i(0, 0, src->GetWidth(), src->GetHeight());
|
||||
#else
|
||||
const float upscale = GSConfig.UpscaleMultiplier;
|
||||
const GSVector2i size(src->GetSize());
|
||||
const int left = static_cast<int>(static_cast<float>(GSConfig.Crop[0]) * upscale);
|
||||
|
@ -543,7 +540,37 @@ static GSVector4i CalculateDrawSrcRect(const GSTexture* src)
|
|||
const int right = size.x - static_cast<int>(static_cast<float>(GSConfig.Crop[2]) * upscale);
|
||||
const int bottom = size.y - static_cast<int>(static_cast<float>(GSConfig.Crop[3]) * upscale);
|
||||
return GSVector4i(left, top, right, bottom);
|
||||
#endif
|
||||
}
|
||||
|
||||
static const char* GetScreenshotSuffix()
|
||||
{
|
||||
static constexpr const char* suffixes[static_cast<u8>(GSScreenshotFormat::Count)] = {
|
||||
"png", "jpg"};
|
||||
return suffixes[static_cast<u8>(GSConfig.ScreenshotFormat)];
|
||||
}
|
||||
|
||||
static void CompressAndWriteScreenshot(std::string filename, u32 width, u32 height, std::vector<u32> pixels)
|
||||
{
|
||||
Common::RGBA8Image image;
|
||||
image.SetPixels(width, height, std::move(pixels));
|
||||
|
||||
std::string key(fmt::format("GSScreenshot_{}", filename));
|
||||
Host::AddIconOSDMessage(key, ICON_FA_CAMERA, fmt::format("Saving screenshot to '{}'.", Path::GetFileName(filename)), 60.0f);
|
||||
|
||||
// maybe std::async would be better here.. but it's definitely worth threading, large screenshots take a while to compress.
|
||||
std::thread compress_thread([key = std::move(key), filename = std::move(filename), image = std::move(image), quality = GSConfig.ScreenshotQuality]() {
|
||||
if (image.SaveToFile(filename.c_str(), quality))
|
||||
{
|
||||
Host::AddIconOSDMessage(std::move(key), ICON_FA_CAMERA,
|
||||
fmt::format("Saved screenshot to '{}'.", Path::GetFileName(filename)), Host::OSD_INFO_DURATION);
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::AddIconOSDMessage(std::move(key), ICON_FA_CAMERA,
|
||||
fmt::format("Failed to save screenshot to '{}'.", Path::GetFileName(filename), Host::OSD_ERROR_DURATION));
|
||||
}
|
||||
});
|
||||
compress_thread.detach();
|
||||
}
|
||||
|
||||
void GSRenderer::VSync(u32 field, bool registers_written)
|
||||
|
@ -654,6 +681,9 @@ void GSRenderer::VSync(u32 field, bool registers_written)
|
|||
|
||||
if (!m_snapshot.empty())
|
||||
{
|
||||
u32 screenshot_width, screenshot_height;
|
||||
std::vector<u32> screenshot_pixels;
|
||||
|
||||
if (!m_dump && m_dump_frames > 0)
|
||||
{
|
||||
freezeData fd = {0, nullptr};
|
||||
|
@ -664,14 +694,14 @@ void GSRenderer::VSync(u32 field, bool registers_written)
|
|||
// keep the screenshot relatively small so we don't bloat the dump
|
||||
static constexpr u32 DUMP_SCREENSHOT_WIDTH = 640;
|
||||
static constexpr u32 DUMP_SCREENSHOT_HEIGHT = 480;
|
||||
std::vector<u32> screenshot_pixels;
|
||||
SaveSnapshotToMemory(DUMP_SCREENSHOT_WIDTH, DUMP_SCREENSHOT_HEIGHT, &screenshot_pixels);
|
||||
SaveSnapshotToMemory(DUMP_SCREENSHOT_WIDTH, DUMP_SCREENSHOT_HEIGHT, true, false,
|
||||
&screenshot_width, &screenshot_height, &screenshot_pixels);
|
||||
|
||||
std::string_view compression_str;
|
||||
if (GSConfig.GSDumpCompression == GSDumpCompressionMethod::Uncompressed)
|
||||
{
|
||||
m_dump = std::unique_ptr<GSDumpBase>(new GSDumpUncompressed(m_snapshot, GetDumpSerial(), m_crc,
|
||||
DUMP_SCREENSHOT_WIDTH, DUMP_SCREENSHOT_HEIGHT,
|
||||
screenshot_width, screenshot_height,
|
||||
screenshot_pixels.empty() ? nullptr : screenshot_pixels.data(),
|
||||
fd, m_regs));
|
||||
compression_str = "with no compression";
|
||||
|
@ -679,7 +709,7 @@ void GSRenderer::VSync(u32 field, bool registers_written)
|
|||
else if (GSConfig.GSDumpCompression == GSDumpCompressionMethod::LZMA)
|
||||
{
|
||||
m_dump = std::unique_ptr<GSDumpBase>(new GSDumpXz(m_snapshot, GetDumpSerial(), m_crc,
|
||||
DUMP_SCREENSHOT_WIDTH, DUMP_SCREENSHOT_HEIGHT,
|
||||
screenshot_width, screenshot_height,
|
||||
screenshot_pixels.empty() ? nullptr : screenshot_pixels.data(),
|
||||
fd, m_regs));
|
||||
compression_str = "with LZMA compression";
|
||||
|
@ -687,7 +717,7 @@ void GSRenderer::VSync(u32 field, bool registers_written)
|
|||
else
|
||||
{
|
||||
m_dump = std::unique_ptr<GSDumpBase>(new GSDumpZst(m_snapshot, GetDumpSerial(), m_crc,
|
||||
DUMP_SCREENSHOT_WIDTH, DUMP_SCREENSHOT_HEIGHT,
|
||||
screenshot_width, screenshot_height,
|
||||
screenshot_pixels.empty() ? nullptr : screenshot_pixels.data(),
|
||||
fd, m_regs));
|
||||
compression_str = "with Zstandard compression";
|
||||
|
@ -700,18 +730,21 @@ void GSRenderer::VSync(u32 field, bool registers_written)
|
|||
Path::GetFileName(m_dump->GetPath())), Host::OSD_INFO_DURATION);
|
||||
}
|
||||
|
||||
if (GSTexture* t = g_gs_device->GetSnapshot())
|
||||
const bool internal_resolution = (GSConfig.ScreenshotSize >= GSScreenshotSize::InternalResolution);
|
||||
const bool aspect_correct = (GSConfig.ScreenshotSize != GSScreenshotSize::InternalResolutionUncorrected);
|
||||
|
||||
if (g_gs_device->GetCurrent() && SaveSnapshotToMemory(
|
||||
internal_resolution ? 0 : g_host_display->GetWindowWidth(),
|
||||
internal_resolution ? 0 : g_host_display->GetWindowHeight(),
|
||||
aspect_correct, true,
|
||||
&screenshot_width, &screenshot_height, &screenshot_pixels))
|
||||
{
|
||||
const std::string path(m_snapshot + ".png");
|
||||
if (t->Save(path))
|
||||
{
|
||||
Host::AddKeyedOSDMessage("GSScreenshot",
|
||||
fmt::format("Screenshot saved to '{}'.", Path::GetFileName(path)), Host::OSD_INFO_DURATION);
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::AddFormattedOSDMessage(Host::OSD_ERROR_DURATION, "Failed to save screenshot to '%s'.", path.c_str());
|
||||
}
|
||||
CompressAndWriteScreenshot(fmt::format("{}.{}", m_snapshot, GetScreenshotSuffix()),
|
||||
screenshot_width, screenshot_height, std::move(screenshot_pixels));
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::AddIconOSDMessage("GSScreenshot", ICON_FA_CAMERA, "Failed to render/download screenshot.", Host::OSD_ERROR_DURATION);
|
||||
}
|
||||
|
||||
m_snapshot = {};
|
||||
|
@ -945,42 +978,69 @@ void GSRenderer::PurgeTextureCache()
|
|||
{
|
||||
}
|
||||
|
||||
bool GSRenderer::SaveSnapshotToMemory(u32 width, u32 height, std::vector<u32>* pixels)
|
||||
bool GSRenderer::SaveSnapshotToMemory(u32 window_width, u32 window_height, bool apply_aspect, bool crop_borders,
|
||||
u32* width, u32* height, std::vector<u32>* pixels)
|
||||
{
|
||||
GSTexture* const current = g_gs_device->GetCurrent();
|
||||
if (!current)
|
||||
{
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
pixels->clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
const GSVector4i src_rect(CalculateDrawSrcRect(current));
|
||||
const GSVector4 src_uv(GSVector4(src_rect) / GSVector4(current->GetSize()).xyxy());
|
||||
GSVector4 draw_rect(CalculateDrawDstRect(width, height, src_rect, current->GetSize(),
|
||||
HostDisplay::Alignment::LeftOrTop, false, GetVideoMode() == GSVideoMode::SDTV_480P || (GSConfig.PCRTCOverscan && GSConfig.PCRTCOffsets)));
|
||||
u32 draw_width = static_cast<u32>(draw_rect.z - draw_rect.x);
|
||||
u32 draw_height = static_cast<u32>(draw_rect.w - draw_rect.y);
|
||||
if (draw_width > width)
|
||||
|
||||
const bool is_progressive = (GetVideoMode() == GSVideoMode::SDTV_480P || (GSConfig.PCRTCOverscan && GSConfig.PCRTCOffsets));
|
||||
GSVector4 draw_rect;
|
||||
if (window_width == 0 || window_height == 0)
|
||||
{
|
||||
draw_width = width;
|
||||
draw_rect.left = 0;
|
||||
draw_rect.right = width;
|
||||
if (apply_aspect)
|
||||
{
|
||||
// use internal resolution of the texture
|
||||
const float aspect = GetCurrentAspectRatioFloat(is_progressive);
|
||||
const int tex_width = current->GetWidth();
|
||||
const int tex_height = current->GetHeight();
|
||||
|
||||
// expand to the larger dimension
|
||||
const float tex_aspect = static_cast<float>(tex_width) / static_cast<float>(tex_height);
|
||||
if (tex_aspect >= aspect)
|
||||
draw_rect = GSVector4(0.0f, 0.0f, static_cast<float>(tex_width), static_cast<float>(tex_width) / aspect);
|
||||
else
|
||||
draw_rect = GSVector4(0.0f, 0.0f, static_cast<float>(tex_height) * aspect, static_cast<float>(tex_height));
|
||||
}
|
||||
else
|
||||
{
|
||||
// uncorrected aspect is only available at internal resolution
|
||||
draw_rect = GSVector4(0.0f, 0.0f, static_cast<float>(current->GetWidth()), static_cast<float>(current->GetHeight()));
|
||||
}
|
||||
}
|
||||
if (draw_height > height)
|
||||
else
|
||||
{
|
||||
draw_height = height;
|
||||
draw_rect.top = 0;
|
||||
draw_rect.bottom = height;
|
||||
draw_rect = CalculateDrawDstRect(window_width, window_height, src_rect, current->GetSize(),
|
||||
HostDisplay::Alignment::LeftOrTop, false, is_progressive);
|
||||
}
|
||||
const u32 draw_width = static_cast<u32>(draw_rect.z - draw_rect.x);
|
||||
const u32 draw_height = static_cast<u32>(draw_rect.w - draw_rect.y);
|
||||
const u32 image_width = crop_borders ? draw_width : std::max(draw_width, window_width);
|
||||
const u32 image_height = crop_borders ? draw_height : std::max(draw_height, window_height);
|
||||
|
||||
GSTexture::GSMap map;
|
||||
const bool result = g_gs_device->DownloadTextureConvert(
|
||||
current, src_uv,
|
||||
GSVector2i(draw_width, draw_height), GSTexture::Format::Color,
|
||||
ShaderConvert::COPY, map, true);
|
||||
ShaderConvert::TRANSPARENCY_FILTER, map, true);
|
||||
if (result)
|
||||
{
|
||||
const u32 pad_x = (width - draw_width) / 2;
|
||||
const u32 pad_y = (height - draw_height) / 2;
|
||||
pixels->resize(width * height, 0);
|
||||
StringUtil::StrideMemCpy(pixels->data() + pad_y * width + pad_x, width * sizeof(u32),
|
||||
const u32 pad_x = (image_width - draw_width) / 2;
|
||||
const u32 pad_y = (image_height - draw_height) / 2;
|
||||
pixels->clear();
|
||||
pixels->resize(image_width * image_height, 0);
|
||||
*width = image_width;
|
||||
*height = image_height;
|
||||
StringUtil::StrideMemCpy(pixels->data() + pad_y * image_width + pad_x, image_width * sizeof(u32),
|
||||
map.bits, map.pitch, draw_width * sizeof(u32), draw_height);
|
||||
|
||||
g_gs_device->DownloadTextureComplete();
|
||||
|
|
|
@ -44,7 +44,7 @@ private:
|
|||
|
||||
protected:
|
||||
GSVector2i m_real_size{0, 0};
|
||||
bool m_texture_shuffle;
|
||||
bool m_texture_shuffle = false;
|
||||
|
||||
virtual GSTexture* GetOutput(int i, int& y_offset) = 0;
|
||||
virtual GSTexture* GetFeedbackOutput() { return nullptr; }
|
||||
|
@ -66,7 +66,8 @@ public:
|
|||
virtual void PurgePool() override;
|
||||
virtual void PurgeTextureCache();
|
||||
|
||||
bool SaveSnapshotToMemory(u32 width, u32 height, std::vector<u32>* pixels);
|
||||
bool SaveSnapshotToMemory(u32 window_width, u32 window_height, bool apply_aspect, bool crop_borders,
|
||||
u32* width, u32* height, std::vector<u32>* pixels);
|
||||
|
||||
void QueueSnapshot(const std::string& path, u32 gsdump_frames);
|
||||
void StopGSDump();
|
||||
|
|
|
@ -126,8 +126,7 @@ fragment float4 ps_hdr_resolve(float4 p [[position]], DirectReadTextureIn<float>
|
|||
fragment float4 ps_filter_transparency(ConvertShaderData data [[stage_in]], ConvertPSRes res)
|
||||
{
|
||||
float4 c = res.sample(data.t);
|
||||
c.a = dot(c.rgb, float3(0.299f, 0.587f, 0.114f));
|
||||
return c;
|
||||
return float4(c.rgb, 1.0);
|
||||
}
|
||||
|
||||
fragment uint ps_convert_float32_32bits(ConvertShaderData data [[stage_in]], ConvertPSDepthRes res)
|
||||
|
|
|
@ -1029,11 +1029,12 @@ void SysMtgsThread::ToggleSoftwareRendering()
|
|||
SetSoftwareRendering(GSConfig.Renderer != GSRendererType::SW);
|
||||
}
|
||||
|
||||
bool SysMtgsThread::SaveMemorySnapshot(u32 width, u32 height, std::vector<u32>* pixels)
|
||||
bool SysMtgsThread::SaveMemorySnapshot(u32 window_width, u32 window_height, bool apply_aspect, bool crop_borders,
|
||||
u32* width, u32* height, std::vector<u32>* pixels)
|
||||
{
|
||||
bool result = false;
|
||||
RunOnGSThread([width, height, pixels, &result]() {
|
||||
result = GSSaveSnapshotToMemory(width, height, pixels);
|
||||
RunOnGSThread([window_width, window_height, apply_aspect, crop_borders, width, height, pixels, &result]() {
|
||||
result = GSSaveSnapshotToMemory(window_width, window_height, apply_aspect, crop_borders, width, height, pixels);
|
||||
});
|
||||
WaitGS(false, false, false);
|
||||
return result;
|
||||
|
|
|
@ -425,6 +425,11 @@ bool Pcsx2Config::GSOptions::OptionsAreEqual(const GSOptions& right) const
|
|||
OpEqu(ShadeBoost_Saturation) &&
|
||||
OpEqu(SaveN) &&
|
||||
OpEqu(SaveL) &&
|
||||
|
||||
OpEqu(ScreenshotSize) &&
|
||||
OpEqu(ScreenshotFormat) &&
|
||||
OpEqu(ScreenshotQuality) &&
|
||||
|
||||
OpEqu(Adapter));
|
||||
}
|
||||
|
||||
|
@ -468,6 +473,9 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap)
|
|||
SettingsWrapBitBool(SyncToHostRefreshRate);
|
||||
SettingsWrapEnumEx(AspectRatio, "AspectRatio", AspectRatioNames);
|
||||
SettingsWrapEnumEx(FMVAspectRatioSwitch, "FMVAspectRatioSwitch", FMVAspectRatioSwitchNames);
|
||||
SettingsWrapIntEnumEx(ScreenshotSize, "ScreenshotSize");
|
||||
SettingsWrapIntEnumEx(ScreenshotFormat, "ScreenshotFormat");
|
||||
SettingsWrapEntry(ScreenshotQuality);
|
||||
SettingsWrapEntry(StretchY);
|
||||
SettingsWrapEntryEx(Crop[0], "CropLeft");
|
||||
SettingsWrapEntryEx(Crop[1], "CropTop");
|
||||
|
|
|
@ -775,16 +775,17 @@ std::unique_ptr<SaveStateScreenshotData> SaveState_SaveScreenshot()
|
|||
static constexpr u32 SCREENSHOT_WIDTH = 640;
|
||||
static constexpr u32 SCREENSHOT_HEIGHT = 480;
|
||||
|
||||
std::vector<u32> pixels(SCREENSHOT_WIDTH * SCREENSHOT_HEIGHT);
|
||||
if (!GetMTGS().SaveMemorySnapshot(SCREENSHOT_WIDTH, SCREENSHOT_HEIGHT, &pixels))
|
||||
u32 width, height;
|
||||
std::vector<u32> pixels;
|
||||
if (!GetMTGS().SaveMemorySnapshot(SCREENSHOT_WIDTH, SCREENSHOT_HEIGHT, true, false, &width, &height, &pixels))
|
||||
{
|
||||
// saving failed for some reason, device lost?
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<SaveStateScreenshotData> data = std::make_unique<SaveStateScreenshotData>();
|
||||
data->width = SCREENSHOT_WIDTH;
|
||||
data->height = SCREENSHOT_HEIGHT;
|
||||
data->width = width;
|
||||
data->height = height;
|
||||
data->pixels = std::move(pixels);
|
||||
return data;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue