GS: Add internal resolution screenshot option

This commit is contained in:
Connor McLaughlin 2022-12-10 19:11:08 +10:00 committed by refractionpcsx2
parent a67d3e9aee
commit 354951f1d6
15 changed files with 238 additions and 71 deletions

View File

@ -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(""));

View File

@ -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">

View File

@ -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();

View File

@ -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,

View File

@ -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:

View File

@ -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, ...)

View File

@ -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
{

View File

@ -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)

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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)

View File

@ -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;

View File

@ -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");

View File

@ -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;
}