HostDisplay: Treat internal res screenshots as a screenshot

This commit is contained in:
Stenzek 2023-05-01 15:12:30 +10:00
parent a003832e33
commit 6b366afb05
11 changed files with 124 additions and 77 deletions

View File

@ -232,18 +232,20 @@ void HostDisplay::CalculateDrawRect(s32 window_width, s32 window_height, float*
apply_aspect_ratio ? apply_aspect_ratio ?
(display_aspect_ratio / (static_cast<float>(m_display_width) / static_cast<float>(m_display_height))) : (display_aspect_ratio / (static_cast<float>(m_display_width) / static_cast<float>(m_display_height))) :
1.0f; 1.0f;
const float display_width = g_settings.display_stretch_vertically ? const float display_width = g_settings.display_stretch_vertically ? static_cast<float>(m_display_width) :
static_cast<float>(m_display_width) : static_cast<float>(m_display_width) * x_scale; static_cast<float>(m_display_width) * x_scale;
const float display_height = g_settings.display_stretch_vertically ? const float display_height = g_settings.display_stretch_vertically ? static_cast<float>(m_display_height) / x_scale :
static_cast<float>(m_display_height) / x_scale : static_cast<float>(m_display_height); static_cast<float>(m_display_height);
const float active_left = g_settings.display_stretch_vertically ? const float active_left = g_settings.display_stretch_vertically ? static_cast<float>(m_display_active_left) :
static_cast<float>(m_display_active_left) : static_cast<float>(m_display_active_left) * x_scale; static_cast<float>(m_display_active_left) * x_scale;
const float active_top = g_settings.display_stretch_vertically ? 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) / x_scale : static_cast<float>(m_display_active_top); static_cast<float>(m_display_active_top);
const float active_width = g_settings.display_stretch_vertically ? 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; 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 ? 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); static_cast<float>(m_display_active_height) / x_scale :
static_cast<float>(m_display_active_height);
if (out_x_scale) if (out_x_scale)
*out_x_scale = x_scale; *out_x_scale = x_scale;
@ -500,11 +502,13 @@ bool HostDisplay::WriteDisplayTextureToFile(std::string filename, bool full_reso
const float ss_height_scale = static_cast<float>(m_display_active_height) / static_cast<float>(m_display_height); 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; const float ss_aspect_ratio = m_display_aspect_ratio * ss_width_scale / ss_height_scale;
resize_width = g_settings.display_stretch_vertically ? resize_width = g_settings.display_stretch_vertically ?
m_display_texture_view_width : static_cast<s32>(static_cast<float>(resize_height) * ss_aspect_ratio); m_display_texture_view_width :
static_cast<s32>(static_cast<float>(resize_height) * ss_aspect_ratio);
resize_height = g_settings.display_stretch_vertically ? resize_height = g_settings.display_stretch_vertically ?
static_cast<s32>(static_cast<float>(resize_height) / static_cast<s32>(static_cast<float>(resize_height) /
(m_display_aspect_ratio / (static_cast<float>(m_display_width) / static_cast<float>(m_display_height)))) : (m_display_aspect_ratio /
resize_height; (static_cast<float>(m_display_width) / static_cast<float>(m_display_height)))) :
resize_height;
} }
else else
{ {
@ -614,17 +618,65 @@ bool HostDisplay::WriteDisplayTextureToBuffer(std::vector<u32>* buffer, u32 resi
return true; return true;
} }
bool HostDisplay::WriteScreenshotToFile(std::string filename, bool compress_on_thread /*= false*/) bool HostDisplay::WriteScreenshotToFile(std::string filename, bool internal_resolution /* = false */,
bool compress_on_thread /* = false */)
{ {
const u32 width = m_window_info.surface_width; u32 width = m_window_info.surface_width;
const u32 height = m_window_info.surface_height; 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) if (width == 0 || height == 0)
return false; return false;
std::vector<u32> pixels; std::vector<u32> pixels;
u32 pixels_stride; u32 pixels_stride;
GPUTexture::Format pixels_format; GPUTexture::Format pixels_format;
if (!RenderScreenshot(width, height, &pixels, &pixels_stride, &pixels_format)) if (!RenderScreenshot(width, height,
Common::Rectangle<s32>::FromExtents(draw_top, draw_left, draw_width, draw_height), &pixels,
&pixels_stride, &pixels_format))
{ {
Log_ErrorPrintf("Failed to render %ux%u screenshot", width, height); Log_ErrorPrintf("Failed to render %ux%u screenshot", width, height);
return false; return false;

View File

@ -104,8 +104,8 @@ public:
virtual bool Render(bool skip_present) = 0; virtual bool Render(bool skip_present) = 0;
/// Renders the display with postprocessing to the specified image. /// Renders the display with postprocessing to the specified image.
virtual bool RenderScreenshot(u32 width, u32 height, std::vector<u32>* out_pixels, u32* out_stride, virtual bool RenderScreenshot(u32 width, u32 height, const Common::Rectangle<s32>& draw_rect,
GPUTexture::Format* out_format) = 0; std::vector<u32>* out_pixels, u32* out_stride, GPUTexture::Format* out_format) = 0;
ALWAYS_INLINE bool IsVsyncEnabled() const { return m_vsync_enabled; } ALWAYS_INLINE bool IsVsyncEnabled() const { return m_vsync_enabled; }
virtual void SetVSync(bool enabled) = 0; virtual void SetVSync(bool enabled) = 0;
@ -206,7 +206,7 @@ public:
bool clear_alpha = true); bool clear_alpha = true);
/// Helper function to save screenshot to PNG. /// Helper function to save screenshot to PNG.
bool WriteScreenshotToFile(std::string filename, bool compress_on_thread = false); bool WriteScreenshotToFile(std::string filename, bool internal_resolution = false, bool compress_on_thread = false);
protected: protected:
ALWAYS_INLINE bool HasSoftwareCursor() const { return static_cast<bool>(m_cursor_texture); } ALWAYS_INLINE bool HasSoftwareCursor() const { return static_cast<bool>(m_cursor_texture); }

View File

@ -2069,8 +2069,9 @@ bool System::InternalSaveState(ByteStream* state, u32 screenshot_size /* = 256 *
std::vector<u32> screenshot_buffer; std::vector<u32> screenshot_buffer;
u32 screenshot_stride; u32 screenshot_stride;
GPUTexture::Format screenshot_format; GPUTexture::Format screenshot_format;
if (g_host_display->RenderScreenshot(screenshot_width, screenshot_height, &screenshot_buffer, &screenshot_stride, if (g_host_display->RenderScreenshot(screenshot_width, screenshot_height,
&screenshot_format) && Common::Rectangle<s32>::FromExtents(0, 0, screenshot_width, screenshot_height),
&screenshot_buffer, &screenshot_stride, &screenshot_format) &&
GPUTexture::ConvertTextureDataToRGBA8(screenshot_width, screenshot_height, screenshot_buffer, screenshot_stride, GPUTexture::ConvertTextureDataToRGBA8(screenshot_width, screenshot_height, screenshot_buffer, screenshot_stride,
screenshot_format)) screenshot_format))
{ {
@ -3911,10 +3912,8 @@ bool System::SaveScreenshot(const char* filename /* = nullptr */, bool full_reso
return false; return false;
} }
const bool screenshot_saved = const bool screenshot_saved = g_host_display->WriteScreenshotToFile(
g_settings.display_internal_resolution_screenshots ? filename, g_settings.display_internal_resolution_screenshots, compress_on_thread);
g_host_display->WriteDisplayTextureToFile(filename, full_resolution, apply_aspect_ratio, compress_on_thread) :
g_host_display->WriteScreenshotToFile(filename, compress_on_thread);
if (!screenshot_saved) if (!screenshot_saved)
{ {

View File

@ -705,8 +705,8 @@ bool D3D11HostDisplay::Render(bool skip_present)
return true; return true;
} }
bool D3D11HostDisplay::RenderScreenshot(u32 width, u32 height, std::vector<u32>* out_pixels, u32* out_stride, bool D3D11HostDisplay::RenderScreenshot(u32 width, u32 height, const Common::Rectangle<s32>& draw_rect,
GPUTexture::Format* out_format) std::vector<u32>* out_pixels, u32* out_stride, GPUTexture::Format* out_format)
{ {
static constexpr GPUTexture::Format hdformat = GPUTexture::Format::RGBA8; static constexpr GPUTexture::Format hdformat = GPUTexture::Format::RGBA8;
@ -720,20 +720,18 @@ bool D3D11HostDisplay::RenderScreenshot(u32 width, u32 height, std::vector<u32>*
if (HasDisplayTexture()) if (HasDisplayTexture())
{ {
const auto [left, top, draw_width, draw_height] = CalculateDrawRect(width, height);
if (!m_post_processing_chain.IsEmpty()) if (!m_post_processing_chain.IsEmpty())
{ {
ApplyPostProcessingChain(render_texture.GetD3DRTV(), left, top, draw_width, draw_height, ApplyPostProcessingChain(render_texture.GetD3DRTV(), draw_rect.left, draw_rect.top, draw_rect.GetWidth(),
static_cast<D3D11::Texture*>(m_display_texture), m_display_texture_view_x, draw_rect.GetHeight(), static_cast<D3D11::Texture*>(m_display_texture),
m_display_texture_view_y, m_display_texture_view_width, m_display_texture_view_height, m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width,
width, height); m_display_texture_view_height, width, height);
} }
else else
{ {
RenderDisplay(left, top, draw_width, draw_height, static_cast<D3D11::Texture*>(m_display_texture), RenderDisplay(draw_rect.left, draw_rect.top, draw_rect.GetWidth(), draw_rect.GetHeight(),
m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width, static_cast<D3D11::Texture*>(m_display_texture), m_display_texture_view_x, m_display_texture_view_y,
m_display_texture_view_height, IsUsingLinearFiltering()); m_display_texture_view_width, m_display_texture_view_height, IsUsingLinearFiltering());
} }
} }

View File

@ -67,8 +67,8 @@ public:
void SetVSync(bool enabled) override; void SetVSync(bool enabled) override;
bool Render(bool skip_present) override; bool Render(bool skip_present) override;
bool RenderScreenshot(u32 width, u32 height, std::vector<u32>* out_pixels, u32* out_stride, bool RenderScreenshot(u32 width, u32 height, const Common::Rectangle<s32>& draw_rect, std::vector<u32>* out_pixels,
GPUTexture::Format* out_format) override; u32* out_stride, GPUTexture::Format* out_format) override;
static AdapterAndModeList StaticGetAdapterAndModeList(); static AdapterAndModeList StaticGetAdapterAndModeList();

View File

@ -615,8 +615,8 @@ bool D3D12HostDisplay::Render(bool skip_present)
return true; return true;
} }
bool D3D12HostDisplay::RenderScreenshot(u32 width, u32 height, std::vector<u32>* out_pixels, u32* out_stride, bool D3D12HostDisplay::RenderScreenshot(u32 width, u32 height, const Common::Rectangle<s32>& draw_rect,
GPUTexture::Format* out_format) std::vector<u32>* out_pixels, u32* out_stride, GPUTexture::Format* out_format)
{ {
static constexpr DXGI_FORMAT format = DXGI_FORMAT_R8G8B8A8_UNORM; static constexpr DXGI_FORMAT format = DXGI_FORMAT_R8G8B8A8_UNORM;
static constexpr GPUTexture::Format hdformat = GPUTexture::Format::RGBA8; static constexpr GPUTexture::Format hdformat = GPUTexture::Format::RGBA8;
@ -630,14 +630,13 @@ bool D3D12HostDisplay::RenderScreenshot(u32 width, u32 height, std::vector<u32>*
} }
ID3D12GraphicsCommandList* cmdlist = g_d3d12_context->GetCommandList(); ID3D12GraphicsCommandList* cmdlist = g_d3d12_context->GetCommandList();
const auto [left, top, draw_width, draw_height] = CalculateDrawRect(width, height);
if (HasDisplayTexture() && !m_post_processing_chain.IsEmpty()) if (HasDisplayTexture() && !m_post_processing_chain.IsEmpty())
{ {
ApplyPostProcessingChain(cmdlist, &render_texture, left, top, width, height, ApplyPostProcessingChain(cmdlist, &render_texture, draw_rect.left, draw_rect.top, draw_rect.GetWidth(),
static_cast<D3D12::Texture*>(m_display_texture), m_display_texture_view_x, draw_rect.GetHeight(), static_cast<D3D12::Texture*>(m_display_texture),
m_display_texture_view_y, m_display_texture_view_width, m_display_texture_view_height, m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width,
width, height); m_display_texture_view_height, width, height);
} }
else else
{ {
@ -647,9 +646,9 @@ bool D3D12HostDisplay::RenderScreenshot(u32 width, u32 height, std::vector<u32>*
if (HasDisplayTexture()) if (HasDisplayTexture())
{ {
RenderDisplay(cmdlist, left, top, draw_width, draw_height, static_cast<D3D12::Texture*>(m_display_texture), RenderDisplay(cmdlist, draw_rect.left, draw_rect.top, draw_rect.GetWidth(), draw_rect.GetHeight(),
m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width, static_cast<D3D12::Texture*>(m_display_texture), m_display_texture_view_x, m_display_texture_view_y,
m_display_texture_view_height, IsUsingLinearFiltering()); m_display_texture_view_width, m_display_texture_view_height, IsUsingLinearFiltering());
} }
} }

View File

@ -66,8 +66,8 @@ public:
void SetVSync(bool enabled) override; void SetVSync(bool enabled) override;
bool Render(bool skip_present) override; bool Render(bool skip_present) override;
bool RenderScreenshot(u32 width, u32 height, std::vector<u32>* out_pixels, u32* out_stride, bool RenderScreenshot(u32 width, u32 height, const Common::Rectangle<s32>& draw_rect, std::vector<u32>* out_pixels,
GPUTexture::Format* out_format) override; u32* out_stride, GPUTexture::Format* out_format) override;
bool SetGPUTimingEnabled(bool enabled) override; bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override; float GetAndResetAccumulatedGPUTime() override;

View File

@ -680,8 +680,8 @@ bool OpenGLHostDisplay::Render(bool skip_present)
return true; return true;
} }
bool OpenGLHostDisplay::RenderScreenshot(u32 width, u32 height, std::vector<u32>* out_pixels, u32* out_stride, bool OpenGLHostDisplay::RenderScreenshot(u32 width, u32 height, const Common::Rectangle<s32>& draw_rect,
GPUTexture::Format* out_format) std::vector<u32>* out_pixels, u32* out_stride, GPUTexture::Format* out_format)
{ {
GL::Texture texture; GL::Texture texture;
if (!texture.Create(width, height, 1, 1, 1, GPUTexture::Format::RGBA8, nullptr, 0) || !texture.CreateFramebuffer()) if (!texture.Create(width, height, 1, 1, 1, GPUTexture::Format::RGBA8, nullptr, 0) || !texture.CreateFramebuffer())
@ -692,14 +692,13 @@ bool OpenGLHostDisplay::RenderScreenshot(u32 width, u32 height, std::vector<u32>
glDisable(GL_SCISSOR_TEST); glDisable(GL_SCISSOR_TEST);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
const auto [left, top, draw_width, draw_height] = CalculateDrawRect(width, height);
if (HasDisplayTexture() && !m_post_processing_chain.IsEmpty()) if (HasDisplayTexture() && !m_post_processing_chain.IsEmpty())
{ {
ApplyPostProcessingChain(texture.GetGLFramebufferID(), left, height - top - draw_height, draw_width, draw_height, ApplyPostProcessingChain(texture.GetGLFramebufferID(), draw_rect.left,
static_cast<GL::Texture*>(m_display_texture), m_display_texture_view_x, height - draw_rect.top - draw_rect.GetHeight(), draw_rect.GetWidth(),
m_display_texture_view_y, m_display_texture_view_width, m_display_texture_view_height, draw_rect.GetHeight(), static_cast<GL::Texture*>(m_display_texture),
width, height); m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width,
m_display_texture_view_height, width, height);
} }
else else
{ {
@ -708,9 +707,10 @@ bool OpenGLHostDisplay::RenderScreenshot(u32 width, u32 height, std::vector<u32>
if (HasDisplayTexture()) if (HasDisplayTexture())
{ {
RenderDisplay(left, height - top - draw_height, draw_width, draw_height, RenderDisplay(draw_rect.left, height - draw_rect.top - draw_rect.GetHeight(), draw_rect.GetWidth(),
static_cast<GL::Texture*>(m_display_texture), m_display_texture_view_x, m_display_texture_view_y, draw_rect.GetHeight(), static_cast<GL::Texture*>(m_display_texture), m_display_texture_view_x,
m_display_texture_view_width, m_display_texture_view_height, IsUsingLinearFiltering()); m_display_texture_view_y, m_display_texture_view_width, m_display_texture_view_height,
IsUsingLinearFiltering());
} }
} }

View File

@ -55,8 +55,8 @@ public:
void SetVSync(bool enabled) override; void SetVSync(bool enabled) override;
bool Render(bool skip_present) override; bool Render(bool skip_present) override;
bool RenderScreenshot(u32 width, u32 height, std::vector<u32>* out_pixels, u32* out_stride, bool RenderScreenshot(u32 width, u32 height, const Common::Rectangle<s32>& draw_rect, std::vector<u32>* out_pixels,
GPUTexture::Format* out_format) override; u32* out_stride, GPUTexture::Format* out_format) override;
bool SetGPUTimingEnabled(bool enabled) override; bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override; float GetAndResetAccumulatedGPUTime() override;

View File

@ -677,8 +677,8 @@ bool VulkanHostDisplay::Render(bool skip_present)
return true; return true;
} }
bool VulkanHostDisplay::RenderScreenshot(u32 width, u32 height, std::vector<u32>* out_pixels, u32* out_stride, bool VulkanHostDisplay::RenderScreenshot(u32 width, u32 height, const Common::Rectangle<s32>& draw_rect,
GPUTexture::Format* out_format) std::vector<u32>* out_pixels, u32* out_stride, GPUTexture::Format* out_format)
{ {
// in theory we could do this without a swap chain, but postprocessing assumes it for now... // in theory we could do this without a swap chain, but postprocessing assumes it for now...
if (!m_swap_chain) if (!m_swap_chain)
@ -746,20 +746,19 @@ bool VulkanHostDisplay::RenderScreenshot(u32 width, u32 height, std::vector<u32>
"VulkanHostDisplay::RenderScreenshot: %ux%u", width, height); "VulkanHostDisplay::RenderScreenshot: %ux%u", width, height);
tex.TransitionToLayout(g_vulkan_context->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); tex.TransitionToLayout(g_vulkan_context->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
const auto [left, top, draw_width, draw_height] = CalculateDrawRect(width, height);
if (!m_post_processing_chain.IsEmpty()) if (!m_post_processing_chain.IsEmpty())
{ {
ApplyPostProcessingChain(fb, left, top, draw_width, draw_height, static_cast<Vulkan::Texture*>(m_display_texture), ApplyPostProcessingChain(fb, draw_rect.left, draw_rect.top, draw_rect.GetWidth(), draw_rect.GetHeight(),
m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width, static_cast<Vulkan::Texture*>(m_display_texture), m_display_texture_view_x,
m_display_texture_view_height, width, height); m_display_texture_view_y, m_display_texture_view_width, m_display_texture_view_height,
width, height);
} }
else else
{ {
BeginSwapChainRenderPass(fb, width, height); BeginSwapChainRenderPass(fb, width, height);
RenderDisplay(left, top, draw_width, draw_height, static_cast<Vulkan::Texture*>(m_display_texture), RenderDisplay(draw_rect.left, draw_rect.top, draw_rect.GetWidth(), draw_rect.GetHeight(),
m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width, static_cast<Vulkan::Texture*>(m_display_texture), m_display_texture_view_x, m_display_texture_view_y,
m_display_texture_view_height, IsUsingLinearFiltering()); m_display_texture_view_width, m_display_texture_view_height, IsUsingLinearFiltering());
} }
vkCmdEndRenderPass(g_vulkan_context->GetCurrentCommandBuffer()); vkCmdEndRenderPass(g_vulkan_context->GetCurrentCommandBuffer());

View File

@ -59,8 +59,8 @@ public:
void SetVSync(bool enabled) override; void SetVSync(bool enabled) override;
bool Render(bool skip_present) override; bool Render(bool skip_present) override;
bool RenderScreenshot(u32 width, u32 height, std::vector<u32>* out_pixels, u32* out_stride, bool RenderScreenshot(u32 width, u32 height, const Common::Rectangle<s32>& draw_rect, std::vector<u32>* out_pixels,
GPUTexture::Format* out_format) override; u32* out_stride, GPUTexture::Format* out_format) override;
bool SetGPUTimingEnabled(bool enabled) override; bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override; float GetAndResetAccumulatedGPUTime() override;