|
|
|
|
@ -2,17 +2,12 @@
|
|
|
|
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
|
|
|
|
|
|
|
|
|
#include "gpu_device.h"
|
|
|
|
|
#include "core/host.h"
|
|
|
|
|
#include "core/settings.h"
|
|
|
|
|
#include "core/system.h"
|
|
|
|
|
#include "postprocessing_chain.h"
|
|
|
|
|
#include "core/host.h" // TODO: Remove, needed for getting fullscreen mode.
|
|
|
|
|
#include "core/settings.h" // TODO: Remove, needed for dump directory.
|
|
|
|
|
#include "shadergen.h"
|
|
|
|
|
|
|
|
|
|
#include "common/align.h"
|
|
|
|
|
#include "common/assert.h"
|
|
|
|
|
#include "common/file_system.h"
|
|
|
|
|
#include "common/hash_combine.h"
|
|
|
|
|
#include "common/heap_array.h"
|
|
|
|
|
#include "common/log.h"
|
|
|
|
|
#include "common/path.h"
|
|
|
|
|
#include "common/string_util.h"
|
|
|
|
|
@ -20,14 +15,6 @@
|
|
|
|
|
|
|
|
|
|
#include "fmt/format.h"
|
|
|
|
|
#include "imgui.h"
|
|
|
|
|
#include "stb_image_resize.h"
|
|
|
|
|
#include "stb_image_write.h"
|
|
|
|
|
|
|
|
|
|
#include <cerrno>
|
|
|
|
|
#include <cmath>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
#include <thread>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
Log_SetChannel(GPUDevice);
|
|
|
|
|
|
|
|
|
|
@ -258,7 +245,6 @@ bool GPUDevice::Create(const std::string_view& adapter, const std::string_view&
|
|
|
|
|
|
|
|
|
|
void GPUDevice::Destroy()
|
|
|
|
|
{
|
|
|
|
|
m_post_processing_chain.reset();
|
|
|
|
|
if (HasSurface())
|
|
|
|
|
DestroySurface();
|
|
|
|
|
DestroyResources();
|
|
|
|
|
@ -416,32 +402,6 @@ bool GPUDevice::CreateResources()
|
|
|
|
|
|
|
|
|
|
ShaderGen shadergen(GetRenderAPI(), m_features.dual_source_blend);
|
|
|
|
|
|
|
|
|
|
GPUPipeline::GraphicsConfig plconfig;
|
|
|
|
|
plconfig.layout = GPUPipeline::Layout::SingleTextureAndPushConstants;
|
|
|
|
|
plconfig.input_layout.vertex_stride = 0;
|
|
|
|
|
plconfig.primitive = GPUPipeline::Primitive::Triangles;
|
|
|
|
|
plconfig.rasterization = GPUPipeline::RasterizationState::GetNoCullState();
|
|
|
|
|
plconfig.depth = GPUPipeline::DepthState::GetNoTestsState();
|
|
|
|
|
plconfig.blend = GPUPipeline::BlendState::GetNoBlendingState();
|
|
|
|
|
plconfig.color_format = HasSurface() ? m_window_info.surface_format : GPUTexture::Format::RGBA8;
|
|
|
|
|
plconfig.depth_format = GPUTexture::Format::Unknown;
|
|
|
|
|
plconfig.samples = 1;
|
|
|
|
|
plconfig.per_sample_shading = false;
|
|
|
|
|
|
|
|
|
|
std::unique_ptr<GPUShader> display_vs = CreateShader(GPUShaderStage::Vertex, shadergen.GenerateDisplayVertexShader());
|
|
|
|
|
std::unique_ptr<GPUShader> display_fs =
|
|
|
|
|
CreateShader(GPUShaderStage::Fragment, shadergen.GenerateDisplayFragmentShader(true));
|
|
|
|
|
if (!display_vs || !display_fs)
|
|
|
|
|
return false;
|
|
|
|
|
GL_OBJECT_NAME(display_vs, "Display Vertex Shader");
|
|
|
|
|
GL_OBJECT_NAME(display_fs, "Display Fragment Shader");
|
|
|
|
|
|
|
|
|
|
plconfig.vertex_shader = display_vs.get();
|
|
|
|
|
plconfig.fragment_shader = display_fs.get();
|
|
|
|
|
if (!(m_display_pipeline = CreatePipeline(plconfig)))
|
|
|
|
|
return false;
|
|
|
|
|
GL_OBJECT_NAME(m_display_pipeline, "Display Pipeline");
|
|
|
|
|
|
|
|
|
|
std::unique_ptr<GPUShader> imgui_vs = CreateShader(GPUShaderStage::Vertex, shadergen.GenerateImGuiVertexShader());
|
|
|
|
|
std::unique_ptr<GPUShader> imgui_fs = CreateShader(GPUShaderStage::Fragment, shadergen.GenerateImGuiFragmentShader());
|
|
|
|
|
if (!imgui_vs || !imgui_fs)
|
|
|
|
|
@ -458,11 +418,20 @@ bool GPUDevice::CreateResources()
|
|
|
|
|
GPUPipeline::VertexAttribute::Type::UNorm8, 4, offsetof(ImDrawVert, col)),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
GPUPipeline::GraphicsConfig plconfig;
|
|
|
|
|
plconfig.layout = GPUPipeline::Layout::SingleTextureAndPushConstants;
|
|
|
|
|
plconfig.input_layout.vertex_attributes = imgui_attributes;
|
|
|
|
|
plconfig.input_layout.vertex_stride = sizeof(ImDrawVert);
|
|
|
|
|
plconfig.primitive = GPUPipeline::Primitive::Triangles;
|
|
|
|
|
plconfig.rasterization = GPUPipeline::RasterizationState::GetNoCullState();
|
|
|
|
|
plconfig.depth = GPUPipeline::DepthState::GetNoTestsState();
|
|
|
|
|
plconfig.blend = GPUPipeline::BlendState::GetAlphaBlendingState();
|
|
|
|
|
plconfig.color_format = HasSurface() ? m_window_info.surface_format : GPUTexture::Format::RGBA8;
|
|
|
|
|
plconfig.depth_format = GPUTexture::Format::Unknown;
|
|
|
|
|
plconfig.samples = 1;
|
|
|
|
|
plconfig.per_sample_shading = false;
|
|
|
|
|
plconfig.vertex_shader = imgui_vs.get();
|
|
|
|
|
plconfig.fragment_shader = imgui_fs.get();
|
|
|
|
|
plconfig.blend = GPUPipeline::BlendState::GetAlphaBlendingState();
|
|
|
|
|
|
|
|
|
|
m_imgui_pipeline = CreatePipeline(plconfig);
|
|
|
|
|
if (!m_imgui_pipeline)
|
|
|
|
|
@ -480,7 +449,6 @@ void GPUDevice::DestroyResources()
|
|
|
|
|
m_imgui_font_texture.reset();
|
|
|
|
|
m_imgui_pipeline.reset();
|
|
|
|
|
|
|
|
|
|
m_display_pipeline.reset();
|
|
|
|
|
m_imgui_pipeline.reset();
|
|
|
|
|
|
|
|
|
|
m_linear_sampler.reset();
|
|
|
|
|
@ -489,32 +457,6 @@ void GPUDevice::DestroyResources()
|
|
|
|
|
m_shader_cache.Close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GPUDevice::SetPostProcessingChain(const std::string_view& config)
|
|
|
|
|
{
|
|
|
|
|
m_post_processing_chain.reset();
|
|
|
|
|
|
|
|
|
|
if (config.empty())
|
|
|
|
|
return true;
|
|
|
|
|
else if (m_window_info.surface_format == GPUTexture::Format::Unknown)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
m_post_processing_chain = std::make_unique<PostProcessingChain>();
|
|
|
|
|
if (!m_post_processing_chain->CreateFromString(config) ||
|
|
|
|
|
!m_post_processing_chain->CheckTargets(m_window_info.surface_format, m_window_info.surface_width,
|
|
|
|
|
m_window_info.surface_height))
|
|
|
|
|
{
|
|
|
|
|
m_post_processing_chain.reset();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
else if (m_post_processing_chain->IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
m_post_processing_chain.reset();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GPUDevice::RenderImGui()
|
|
|
|
|
{
|
|
|
|
|
GL_SCOPE("RenderImGui");
|
|
|
|
|
@ -786,45 +728,6 @@ void GPUDevice::ThrottlePresentation()
|
|
|
|
|
Common::Timer::SleepUntil(m_last_frame_displayed_time, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GPUDevice::ClearDisplayTexture()
|
|
|
|
|
{
|
|
|
|
|
m_display_texture = nullptr;
|
|
|
|
|
m_display_texture_view_x = 0;
|
|
|
|
|
m_display_texture_view_y = 0;
|
|
|
|
|
m_display_texture_view_width = 0;
|
|
|
|
|
m_display_texture_view_height = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GPUDevice::SetDisplayTexture(GPUTexture* texture, s32 view_x, s32 view_y, s32 view_width, s32 view_height)
|
|
|
|
|
{
|
|
|
|
|
DebugAssert(texture);
|
|
|
|
|
m_display_texture = texture;
|
|
|
|
|
m_display_texture_view_x = view_x;
|
|
|
|
|
m_display_texture_view_y = view_y;
|
|
|
|
|
m_display_texture_view_width = view_width;
|
|
|
|
|
m_display_texture_view_height = view_height;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GPUDevice::SetDisplayTextureRect(s32 view_x, s32 view_y, s32 view_width, s32 view_height)
|
|
|
|
|
{
|
|
|
|
|
m_display_texture_view_x = view_x;
|
|
|
|
|
m_display_texture_view_y = view_y;
|
|
|
|
|
m_display_texture_view_width = view_width;
|
|
|
|
|
m_display_texture_view_height = view_height;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GPUDevice::SetDisplayParameters(s32 display_width, s32 display_height, s32 active_left, s32 active_top,
|
|
|
|
|
s32 active_width, s32 active_height, float display_aspect_ratio)
|
|
|
|
|
{
|
|
|
|
|
m_display_width = display_width;
|
|
|
|
|
m_display_height = display_height;
|
|
|
|
|
m_display_active_left = active_left;
|
|
|
|
|
m_display_active_top = active_top;
|
|
|
|
|
m_display_active_width = active_width;
|
|
|
|
|
m_display_active_height = active_height;
|
|
|
|
|
m_display_aspect_ratio = display_aspect_ratio;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GPUDevice::GetHostRefreshRate(float* refresh_rate)
|
|
|
|
|
{
|
|
|
|
|
if (m_window_info.surface_refresh_rate > 0.0f)
|
|
|
|
|
@ -846,601 +749,6 @@ float GPUDevice::GetAndResetAccumulatedGPUTime()
|
|
|
|
|
return 0.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GPUDevice::IsUsingLinearFiltering() const
|
|
|
|
|
{
|
|
|
|
|
return g_settings.display_linear_filtering;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GPUDevice::Render(bool skip_present)
|
|
|
|
|
{
|
|
|
|
|
// Moved here because there can be draws after UpdateDisplay().
|
|
|
|
|
if (HasDisplayTexture())
|
|
|
|
|
m_display_texture->MakeReadyForSampling();
|
|
|
|
|
|
|
|
|
|
if (skip_present)
|
|
|
|
|
{
|
|
|
|
|
// Should never return true here..
|
|
|
|
|
if (UNLIKELY(BeginPresent(skip_present)))
|
|
|
|
|
Panic("BeginPresent() returned true when skipping...");
|
|
|
|
|
|
|
|
|
|
// Need to kick ImGui state.
|
|
|
|
|
ImGui::Render();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool render_frame;
|
|
|
|
|
if (HasDisplayTexture())
|
|
|
|
|
{
|
|
|
|
|
const auto [left, top, width, height] = CalculateDrawRect(GetWindowWidth(), GetWindowHeight());
|
|
|
|
|
render_frame = RenderDisplay(nullptr, left, top, width, height, m_display_texture, m_display_texture_view_x,
|
|
|
|
|
m_display_texture_view_y, m_display_texture_view_width, m_display_texture_view_height,
|
|
|
|
|
IsUsingLinearFiltering());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
render_frame = BeginPresent(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!render_frame)
|
|
|
|
|
{
|
|
|
|
|
// Window minimized etc.
|
|
|
|
|
ImGui::Render();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SetViewportAndScissor(0, 0, GetWindowWidth(), GetWindowHeight());
|
|
|
|
|
|
|
|
|
|
RenderImGui();
|
|
|
|
|
|
|
|
|
|
EndPresent();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GPUDevice::RenderScreenshot(u32 width, u32 height, const Common::Rectangle<s32>& draw_rect,
|
|
|
|
|
std::vector<u32>* out_pixels, u32* out_stride, GPUTexture::Format* out_format)
|
|
|
|
|
{
|
|
|
|
|
const GPUTexture::Format hdformat = HasSurface() ? m_window_info.surface_format : GPUTexture::Format::RGBA8;
|
|
|
|
|
|
|
|
|
|
std::unique_ptr<GPUTexture> render_texture =
|
|
|
|
|
CreateTexture(width, height, 1, 1, 1, GPUTexture::Type::RenderTarget, hdformat);
|
|
|
|
|
if (!render_texture)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
std::unique_ptr<GPUFramebuffer> render_fb = CreateFramebuffer(render_texture.get());
|
|
|
|
|
if (!render_fb)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
ClearRenderTarget(render_texture.get(), 0);
|
|
|
|
|
|
|
|
|
|
if (m_display_texture)
|
|
|
|
|
{
|
|
|
|
|
RenderDisplay(render_fb.get(), draw_rect.left, draw_rect.top, draw_rect.GetWidth(), draw_rect.GetHeight(),
|
|
|
|
|
m_display_texture, m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width,
|
|
|
|
|
m_display_texture_view_height, IsUsingLinearFiltering());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SetFramebuffer(nullptr);
|
|
|
|
|
|
|
|
|
|
const u32 stride = GPUTexture::GetPixelSize(hdformat) * width;
|
|
|
|
|
out_pixels->resize(width * height);
|
|
|
|
|
if (!DownloadTexture(render_texture.get(), 0, 0, width, height, out_pixels->data(), stride))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
*out_stride = stride;
|
|
|
|
|
*out_format = hdformat;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GPUDevice::RenderDisplay(GPUFramebuffer* target, s32 left, s32 top, s32 width, s32 height, GPUTexture* texture,
|
|
|
|
|
s32 texture_view_x, s32 texture_view_y, s32 texture_view_width, s32 texture_view_height,
|
|
|
|
|
bool linear_filter)
|
|
|
|
|
{
|
|
|
|
|
GL_SCOPE("RenderDisplay: %dx%d at %d,%d", left, top, width, height);
|
|
|
|
|
|
|
|
|
|
const GPUTexture::Format hdformat =
|
|
|
|
|
(target && target->GetRT()) ? target->GetRT()->GetFormat() : m_window_info.surface_format;
|
|
|
|
|
const u32 target_width = target ? target->GetWidth() : m_window_info.surface_width;
|
|
|
|
|
const u32 target_height = target ? target->GetHeight() : m_window_info.surface_height;
|
|
|
|
|
const bool postfx =
|
|
|
|
|
(m_post_processing_chain && m_post_processing_chain->CheckTargets(hdformat, target_width, target_height));
|
|
|
|
|
if (postfx)
|
|
|
|
|
{
|
|
|
|
|
ClearRenderTarget(m_post_processing_chain->GetInputTexture(), 0);
|
|
|
|
|
SetFramebuffer(m_post_processing_chain->GetInputFramebuffer());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (target)
|
|
|
|
|
SetFramebuffer(target);
|
|
|
|
|
else if (!BeginPresent(false))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SetPipeline(m_display_pipeline.get());
|
|
|
|
|
SetTextureSampler(0, texture, linear_filter ? m_linear_sampler.get() : m_nearest_sampler.get());
|
|
|
|
|
|
|
|
|
|
const bool linear = IsUsingLinearFiltering();
|
|
|
|
|
const float position_adjust = linear ? 0.5f : 0.0f;
|
|
|
|
|
const float size_adjust = linear ? 1.0f : 0.0f;
|
|
|
|
|
const float uniforms[4] = {
|
|
|
|
|
(static_cast<float>(texture_view_x) + position_adjust) / static_cast<float>(texture->GetWidth()),
|
|
|
|
|
(static_cast<float>(texture_view_y) + position_adjust) / static_cast<float>(texture->GetHeight()),
|
|
|
|
|
(static_cast<float>(texture_view_width) - size_adjust) / static_cast<float>(texture->GetWidth()),
|
|
|
|
|
(static_cast<float>(texture_view_height) - size_adjust) / static_cast<float>(texture->GetHeight())};
|
|
|
|
|
PushUniformBuffer(uniforms, sizeof(uniforms));
|
|
|
|
|
|
|
|
|
|
SetViewportAndScissor(left, top, width, height);
|
|
|
|
|
Draw(3, 0);
|
|
|
|
|
|
|
|
|
|
if (postfx)
|
|
|
|
|
{
|
|
|
|
|
return m_post_processing_chain->Apply(target, left, top, width, height, texture_view_width, texture_view_height);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GPUDevice::CalculateDrawRect(s32 window_width, s32 window_height, float* out_left, float* out_top,
|
|
|
|
|
float* out_width, float* out_height, float* out_left_padding, float* out_top_padding,
|
|
|
|
|
float* out_scale, float* out_x_scale, bool apply_aspect_ratio /* = true */) const
|
|
|
|
|
{
|
|
|
|
|
const float window_ratio = static_cast<float>(window_width) / static_cast<float>(window_height);
|
|
|
|
|
const float display_aspect_ratio = g_settings.display_stretch ? window_ratio : m_display_aspect_ratio;
|
|
|
|
|
const float x_scale =
|
|
|
|
|
apply_aspect_ratio ?
|
|
|
|
|
(display_aspect_ratio / (static_cast<float>(m_display_width) / static_cast<float>(m_display_height))) :
|
|
|
|
|
1.0f;
|
|
|
|
|
const float display_width = g_settings.display_stretch_vertically ? static_cast<float>(m_display_width) :
|
|
|
|
|
static_cast<float>(m_display_width) * x_scale;
|
|
|
|
|
const float display_height = g_settings.display_stretch_vertically ? static_cast<float>(m_display_height) / x_scale :
|
|
|
|
|
static_cast<float>(m_display_height);
|
|
|
|
|
const float active_left = g_settings.display_stretch_vertically ? static_cast<float>(m_display_active_left) :
|
|
|
|
|
static_cast<float>(m_display_active_left) * x_scale;
|
|
|
|
|
const float active_top = g_settings.display_stretch_vertically ? static_cast<float>(m_display_active_top) / x_scale :
|
|
|
|
|
static_cast<float>(m_display_active_top);
|
|
|
|
|
const float active_width = g_settings.display_stretch_vertically ?
|
|
|
|
|
static_cast<float>(m_display_active_width) :
|
|
|
|
|
static_cast<float>(m_display_active_width) * x_scale;
|
|
|
|
|
const float active_height = g_settings.display_stretch_vertically ?
|
|
|
|
|
static_cast<float>(m_display_active_height) / x_scale :
|
|
|
|
|
static_cast<float>(m_display_active_height);
|
|
|
|
|
if (out_x_scale)
|
|
|
|
|
*out_x_scale = x_scale;
|
|
|
|
|
|
|
|
|
|
// now fit it within the window
|
|
|
|
|
float scale;
|
|
|
|
|
if ((display_width / display_height) >= window_ratio)
|
|
|
|
|
{
|
|
|
|
|
// align in middle vertically
|
|
|
|
|
scale = static_cast<float>(window_width) / display_width;
|
|
|
|
|
if (g_settings.display_integer_scaling)
|
|
|
|
|
scale = std::max(std::floor(scale), 1.0f);
|
|
|
|
|
|
|
|
|
|
if (out_left_padding)
|
|
|
|
|
{
|
|
|
|
|
if (g_settings.display_integer_scaling)
|
|
|
|
|
*out_left_padding = std::max<float>((static_cast<float>(window_width) - display_width * scale) / 2.0f, 0.0f);
|
|
|
|
|
else
|
|
|
|
|
*out_left_padding = 0.0f;
|
|
|
|
|
}
|
|
|
|
|
if (out_top_padding)
|
|
|
|
|
{
|
|
|
|
|
switch (g_settings.display_alignment)
|
|
|
|
|
{
|
|
|
|
|
case DisplayAlignment::RightOrBottom:
|
|
|
|
|
*out_top_padding = std::max<float>(static_cast<float>(window_height) - (display_height * scale), 0.0f);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case DisplayAlignment::Center:
|
|
|
|
|
*out_top_padding =
|
|
|
|
|
std::max<float>((static_cast<float>(window_height) - (display_height * scale)) / 2.0f, 0.0f);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case DisplayAlignment::LeftOrTop:
|
|
|
|
|
default:
|
|
|
|
|
*out_top_padding = 0.0f;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// align in middle horizontally
|
|
|
|
|
scale = static_cast<float>(window_height) / display_height;
|
|
|
|
|
if (g_settings.display_integer_scaling)
|
|
|
|
|
scale = std::max(std::floor(scale), 1.0f);
|
|
|
|
|
|
|
|
|
|
if (out_left_padding)
|
|
|
|
|
{
|
|
|
|
|
switch (g_settings.display_alignment)
|
|
|
|
|
{
|
|
|
|
|
case DisplayAlignment::RightOrBottom:
|
|
|
|
|
*out_left_padding = std::max<float>(static_cast<float>(window_width) - (display_width * scale), 0.0f);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case DisplayAlignment::Center:
|
|
|
|
|
*out_left_padding =
|
|
|
|
|
std::max<float>((static_cast<float>(window_width) - (display_width * scale)) / 2.0f, 0.0f);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case DisplayAlignment::LeftOrTop:
|
|
|
|
|
default:
|
|
|
|
|
*out_left_padding = 0.0f;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (out_top_padding)
|
|
|
|
|
{
|
|
|
|
|
if (g_settings.display_integer_scaling)
|
|
|
|
|
*out_top_padding = std::max<float>((static_cast<float>(window_height) - (display_height * scale)) / 2.0f, 0.0f);
|
|
|
|
|
else
|
|
|
|
|
*out_top_padding = 0.0f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*out_width = active_width * scale;
|
|
|
|
|
*out_height = active_height * scale;
|
|
|
|
|
*out_left = active_left * scale;
|
|
|
|
|
*out_top = active_top * scale;
|
|
|
|
|
if (out_scale)
|
|
|
|
|
*out_scale = scale;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::tuple<s32, s32, s32, s32> GPUDevice::CalculateDrawRect(s32 window_width, s32 window_height,
|
|
|
|
|
bool apply_aspect_ratio /* = true */) const
|
|
|
|
|
{
|
|
|
|
|
float left, top, width, height, left_padding, top_padding;
|
|
|
|
|
CalculateDrawRect(window_width, window_height, &left, &top, &width, &height, &left_padding, &top_padding, nullptr,
|
|
|
|
|
nullptr, apply_aspect_ratio);
|
|
|
|
|
|
|
|
|
|
return std::make_tuple(static_cast<s32>(left + left_padding), static_cast<s32>(top + top_padding),
|
|
|
|
|
static_cast<s32>(width), static_cast<s32>(height));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::tuple<float, float> GPUDevice::ConvertWindowCoordinatesToDisplayCoordinates(s32 window_x, s32 window_y,
|
|
|
|
|
s32 window_width,
|
|
|
|
|
s32 window_height) const
|
|
|
|
|
{
|
|
|
|
|
float left, top, width, height, left_padding, top_padding;
|
|
|
|
|
float scale, x_scale;
|
|
|
|
|
CalculateDrawRect(window_width, window_height, &left, &top, &width, &height, &left_padding, &top_padding, &scale,
|
|
|
|
|
&x_scale);
|
|
|
|
|
|
|
|
|
|
// convert coordinates to active display region, then to full display region
|
|
|
|
|
const float scaled_display_x = static_cast<float>(window_x) - left_padding;
|
|
|
|
|
const float scaled_display_y = static_cast<float>(window_y) - top_padding;
|
|
|
|
|
|
|
|
|
|
// scale back to internal resolution
|
|
|
|
|
const float display_x = scaled_display_x / scale / x_scale;
|
|
|
|
|
const float display_y = scaled_display_y / scale;
|
|
|
|
|
|
|
|
|
|
return std::make_tuple(display_x, display_y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename, FileSystem::ManagedCFilePtr fp,
|
|
|
|
|
bool clear_alpha, bool flip_y, u32 resize_width, u32 resize_height,
|
|
|
|
|
std::vector<u32> texture_data, u32 texture_data_stride,
|
|
|
|
|
GPUTexture::Format texture_format)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
const char* extension = std::strrchr(filename.c_str(), '.');
|
|
|
|
|
if (!extension)
|
|
|
|
|
{
|
|
|
|
|
Log_ErrorPrintf("Unable to determine file extension for '%s'", filename.c_str());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!GPUTexture::ConvertTextureDataToRGBA8(width, height, texture_data, texture_data_stride, texture_format))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (clear_alpha)
|
|
|
|
|
{
|
|
|
|
|
for (u32& pixel : texture_data)
|
|
|
|
|
pixel |= 0xFF000000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (flip_y)
|
|
|
|
|
GPUTexture::FlipTextureDataRGBA8(width, height, texture_data, texture_data_stride);
|
|
|
|
|
|
|
|
|
|
if (resize_width > 0 && resize_height > 0 && (resize_width != width || resize_height != height))
|
|
|
|
|
{
|
|
|
|
|
std::vector<u32> resized_texture_data(resize_width * resize_height);
|
|
|
|
|
u32 resized_texture_stride = sizeof(u32) * resize_width;
|
|
|
|
|
if (!stbir_resize_uint8(reinterpret_cast<u8*>(texture_data.data()), width, height, texture_data_stride,
|
|
|
|
|
reinterpret_cast<u8*>(resized_texture_data.data()), resize_width, resize_height,
|
|
|
|
|
resized_texture_stride, 4))
|
|
|
|
|
{
|
|
|
|
|
Log_ErrorPrintf("Failed to resize texture data from %ux%u to %ux%u", width, height, resize_width, resize_height);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
width = resize_width;
|
|
|
|
|
height = resize_height;
|
|
|
|
|
texture_data = std::move(resized_texture_data);
|
|
|
|
|
texture_data_stride = resized_texture_stride;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto write_func = [](void* context, void* data, int size) {
|
|
|
|
|
std::fwrite(data, 1, size, static_cast<std::FILE*>(context));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
bool result = false;
|
|
|
|
|
if (StringUtil::Strcasecmp(extension, ".png") == 0)
|
|
|
|
|
{
|
|
|
|
|
result =
|
|
|
|
|
(stbi_write_png_to_func(write_func, fp.get(), width, height, 4, texture_data.data(), texture_data_stride) != 0);
|
|
|
|
|
}
|
|
|
|
|
else if (StringUtil::Strcasecmp(extension, ".jpg") == 0)
|
|
|
|
|
{
|
|
|
|
|
result = (stbi_write_jpg_to_func(write_func, fp.get(), width, height, 4, texture_data.data(), 95) != 0);
|
|
|
|
|
}
|
|
|
|
|
else if (StringUtil::Strcasecmp(extension, ".tga") == 0)
|
|
|
|
|
{
|
|
|
|
|
result = (stbi_write_tga_to_func(write_func, fp.get(), width, height, 4, texture_data.data()) != 0);
|
|
|
|
|
}
|
|
|
|
|
else if (StringUtil::Strcasecmp(extension, ".bmp") == 0)
|
|
|
|
|
{
|
|
|
|
|
result = (stbi_write_bmp_to_func(write_func, fp.get(), width, height, 4, texture_data.data()) != 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!result)
|
|
|
|
|
{
|
|
|
|
|
Log_ErrorPrintf("Unknown extension in filename '%s' or save error: '%s'", filename.c_str(), extension);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GPUDevice::WriteTextureToFile(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, std::string filename,
|
|
|
|
|
bool clear_alpha /* = true */, bool flip_y /* = false */, u32 resize_width /* = 0 */,
|
|
|
|
|
u32 resize_height /* = 0 */, bool compress_on_thread /* = false */)
|
|
|
|
|
{
|
|
|
|
|
std::vector<u32> texture_data(width * height);
|
|
|
|
|
u32 texture_data_stride = Common::AlignUpPow2(GPUTexture::GetPixelSize(texture->GetFormat()) * width, 4);
|
|
|
|
|
if (!DownloadTexture(texture, x, y, width, height, texture_data.data(), texture_data_stride))
|
|
|
|
|
{
|
|
|
|
|
Log_ErrorPrintf("Texture download failed");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto fp = FileSystem::OpenManagedCFile(filename.c_str(), "wb");
|
|
|
|
|
if (!fp)
|
|
|
|
|
{
|
|
|
|
|
Log_ErrorPrintf("Can't open file '%s': errno %d", filename.c_str(), errno);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!compress_on_thread)
|
|
|
|
|
{
|
|
|
|
|
return CompressAndWriteTextureToFile(width, height, std::move(filename), std::move(fp), clear_alpha, flip_y,
|
|
|
|
|
resize_width, resize_height, std::move(texture_data), texture_data_stride,
|
|
|
|
|
texture->GetFormat());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::thread compress_thread(CompressAndWriteTextureToFile, width, height, std::move(filename), std::move(fp),
|
|
|
|
|
clear_alpha, flip_y, resize_width, resize_height, std::move(texture_data),
|
|
|
|
|
texture_data_stride, texture->GetFormat());
|
|
|
|
|
compress_thread.detach();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GPUDevice::WriteDisplayTextureToFile(std::string filename, bool full_resolution /* = true */,
|
|
|
|
|
bool apply_aspect_ratio /* = true */, bool compress_on_thread /* = false */)
|
|
|
|
|
{
|
|
|
|
|
if (!m_display_texture)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
s32 resize_width = 0;
|
|
|
|
|
s32 resize_height = std::abs(m_display_texture_view_height);
|
|
|
|
|
if (apply_aspect_ratio)
|
|
|
|
|
{
|
|
|
|
|
const float ss_width_scale = static_cast<float>(m_display_active_width) / static_cast<float>(m_display_width);
|
|
|
|
|
const float ss_height_scale = static_cast<float>(m_display_active_height) / static_cast<float>(m_display_height);
|
|
|
|
|
const float ss_aspect_ratio = m_display_aspect_ratio * ss_width_scale / ss_height_scale;
|
|
|
|
|
resize_width = g_settings.display_stretch_vertically ?
|
|
|
|
|
m_display_texture_view_width :
|
|
|
|
|
static_cast<s32>(static_cast<float>(resize_height) * ss_aspect_ratio);
|
|
|
|
|
resize_height = g_settings.display_stretch_vertically ?
|
|
|
|
|
static_cast<s32>(static_cast<float>(resize_height) /
|
|
|
|
|
(m_display_aspect_ratio /
|
|
|
|
|
(static_cast<float>(m_display_width) / static_cast<float>(m_display_height)))) :
|
|
|
|
|
resize_height;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
resize_width = m_display_texture_view_width;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!full_resolution)
|
|
|
|
|
{
|
|
|
|
|
const s32 resolution_scale = std::abs(m_display_texture_view_height) / m_display_active_height;
|
|
|
|
|
resize_height /= resolution_scale;
|
|
|
|
|
resize_width /= resolution_scale;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (resize_width <= 0 || resize_height <= 0)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
const bool flip_y = (m_display_texture_view_height < 0);
|
|
|
|
|
s32 read_height = m_display_texture_view_height;
|
|
|
|
|
s32 read_y = m_display_texture_view_y;
|
|
|
|
|
if (flip_y)
|
|
|
|
|
{
|
|
|
|
|
read_height = -m_display_texture_view_height;
|
|
|
|
|
read_y =
|
|
|
|
|
(m_display_texture->GetHeight() - read_height) - (m_display_texture->GetHeight() - m_display_texture_view_y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return WriteTextureToFile(m_display_texture, m_display_texture_view_x, read_y, m_display_texture_view_width,
|
|
|
|
|
read_height, std::move(filename), true, flip_y, static_cast<u32>(resize_width),
|
|
|
|
|
static_cast<u32>(resize_height), compress_on_thread);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GPUDevice::WriteDisplayTextureToBuffer(std::vector<u32>* buffer, u32 resize_width /* = 0 */,
|
|
|
|
|
u32 resize_height /* = 0 */, bool clear_alpha /* = true */)
|
|
|
|
|
{
|
|
|
|
|
if (!m_display_texture)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
const bool flip_y = (m_display_texture_view_height < 0);
|
|
|
|
|
s32 read_width = m_display_texture_view_width;
|
|
|
|
|
s32 read_height = m_display_texture_view_height;
|
|
|
|
|
s32 read_x = m_display_texture_view_x;
|
|
|
|
|
s32 read_y = m_display_texture_view_y;
|
|
|
|
|
if (flip_y)
|
|
|
|
|
{
|
|
|
|
|
read_height = -m_display_texture_view_height;
|
|
|
|
|
read_y =
|
|
|
|
|
(m_display_texture->GetHeight() - read_height) - (m_display_texture->GetHeight() - m_display_texture_view_y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u32 width = static_cast<u32>(read_width);
|
|
|
|
|
u32 height = static_cast<u32>(read_height);
|
|
|
|
|
std::vector<u32> texture_data(width * height);
|
|
|
|
|
u32 texture_data_stride = Common::AlignUpPow2(m_display_texture->GetPixelSize() * width, 4);
|
|
|
|
|
if (!DownloadTexture(m_display_texture, read_x, read_y, width, height, texture_data.data(), texture_data_stride))
|
|
|
|
|
{
|
|
|
|
|
Log_ErrorPrintf("Failed to download texture from GPU.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!GPUTexture::ConvertTextureDataToRGBA8(width, height, texture_data, texture_data_stride,
|
|
|
|
|
m_display_texture->GetFormat()))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (clear_alpha)
|
|
|
|
|
{
|
|
|
|
|
for (u32& pixel : texture_data)
|
|
|
|
|
pixel |= 0xFF000000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (flip_y)
|
|
|
|
|
{
|
|
|
|
|
std::vector<u32> temp(width);
|
|
|
|
|
for (u32 flip_row = 0; flip_row < (height / 2); flip_row++)
|
|
|
|
|
{
|
|
|
|
|
u32* top_ptr = &texture_data[flip_row * width];
|
|
|
|
|
u32* bottom_ptr = &texture_data[((height - 1) - flip_row) * width];
|
|
|
|
|
std::memcpy(temp.data(), top_ptr, texture_data_stride);
|
|
|
|
|
std::memcpy(top_ptr, bottom_ptr, texture_data_stride);
|
|
|
|
|
std::memcpy(bottom_ptr, temp.data(), texture_data_stride);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (resize_width > 0 && resize_height > 0 && (resize_width != width || resize_height != height))
|
|
|
|
|
{
|
|
|
|
|
std::vector<u32> resized_texture_data(resize_width * resize_height);
|
|
|
|
|
u32 resized_texture_stride = sizeof(u32) * resize_width;
|
|
|
|
|
if (!stbir_resize_uint8(reinterpret_cast<u8*>(texture_data.data()), width, height, texture_data_stride,
|
|
|
|
|
reinterpret_cast<u8*>(resized_texture_data.data()), resize_width, resize_height,
|
|
|
|
|
resized_texture_stride, 4))
|
|
|
|
|
{
|
|
|
|
|
Log_ErrorPrintf("Failed to resize texture data from %ux%u to %ux%u", width, height, resize_width, resize_height);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
width = resize_width;
|
|
|
|
|
height = resize_height;
|
|
|
|
|
*buffer = std::move(resized_texture_data);
|
|
|
|
|
texture_data_stride = resized_texture_stride;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
*buffer = texture_data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GPUDevice::WriteScreenshotToFile(std::string filename, bool internal_resolution /* = false */,
|
|
|
|
|
bool compress_on_thread /* = false */)
|
|
|
|
|
{
|
|
|
|
|
u32 width = m_window_info.surface_width;
|
|
|
|
|
u32 height = m_window_info.surface_height;
|
|
|
|
|
auto [draw_left, draw_top, draw_width, draw_height] = CalculateDrawRect(width, height);
|
|
|
|
|
|
|
|
|
|
if (internal_resolution && m_display_texture_view_width != 0 && m_display_texture_view_height != 0)
|
|
|
|
|
{
|
|
|
|
|
// If internal res, scale the computed draw rectangle to the internal res.
|
|
|
|
|
// We re-use the draw rect because it's already been AR corrected.
|
|
|
|
|
const float sar =
|
|
|
|
|
static_cast<float>(m_display_texture_view_width) / static_cast<float>(m_display_texture_view_height);
|
|
|
|
|
const float dar = static_cast<float>(draw_width) / static_cast<float>(draw_height);
|
|
|
|
|
if (sar >= dar)
|
|
|
|
|
{
|
|
|
|
|
// stretch height, preserve width
|
|
|
|
|
const float scale = static_cast<float>(m_display_texture_view_width) / static_cast<float>(draw_width);
|
|
|
|
|
width = m_display_texture_view_width;
|
|
|
|
|
height = static_cast<u32>(std::round(static_cast<float>(draw_height) * scale));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// stretch width, preserve height
|
|
|
|
|
const float scale = static_cast<float>(m_display_texture_view_height) / static_cast<float>(draw_height);
|
|
|
|
|
width = static_cast<u32>(std::round(static_cast<float>(draw_width) * scale));
|
|
|
|
|
height = m_display_texture_view_height;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DX11 won't go past 16K texture size.
|
|
|
|
|
constexpr u32 MAX_TEXTURE_SIZE = 16384;
|
|
|
|
|
if (width > MAX_TEXTURE_SIZE)
|
|
|
|
|
{
|
|
|
|
|
height = static_cast<u32>(static_cast<float>(height) /
|
|
|
|
|
(static_cast<float>(width) / static_cast<float>(MAX_TEXTURE_SIZE)));
|
|
|
|
|
width = MAX_TEXTURE_SIZE;
|
|
|
|
|
}
|
|
|
|
|
if (height > MAX_TEXTURE_SIZE)
|
|
|
|
|
{
|
|
|
|
|
height = MAX_TEXTURE_SIZE;
|
|
|
|
|
width = static_cast<u32>(static_cast<float>(width) /
|
|
|
|
|
(static_cast<float>(height) / static_cast<float>(MAX_TEXTURE_SIZE)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove padding, it's not part of the framebuffer.
|
|
|
|
|
draw_left = 0;
|
|
|
|
|
draw_top = 0;
|
|
|
|
|
draw_width = static_cast<s32>(width);
|
|
|
|
|
draw_height = static_cast<s32>(height);
|
|
|
|
|
}
|
|
|
|
|
if (width == 0 || height == 0)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
std::vector<u32> pixels;
|
|
|
|
|
u32 pixels_stride;
|
|
|
|
|
GPUTexture::Format pixels_format;
|
|
|
|
|
if (!RenderScreenshot(width, height,
|
|
|
|
|
Common::Rectangle<s32>::FromExtents(draw_left, draw_top, draw_width, draw_height), &pixels,
|
|
|
|
|
&pixels_stride, &pixels_format))
|
|
|
|
|
{
|
|
|
|
|
Log_ErrorPrintf("Failed to render %ux%u screenshot", width, height);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto fp = FileSystem::OpenManagedCFile(filename.c_str(), "wb");
|
|
|
|
|
if (!fp)
|
|
|
|
|
{
|
|
|
|
|
Log_ErrorPrintf("Can't open file '%s': errno %d", filename.c_str(), errno);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!compress_on_thread)
|
|
|
|
|
{
|
|
|
|
|
return CompressAndWriteTextureToFile(width, height, std::move(filename), std::move(fp), true, UsesLowerLeftOrigin(),
|
|
|
|
|
width, height, std::move(pixels), pixels_stride, pixels_format);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::thread compress_thread(CompressAndWriteTextureToFile, width, height, std::move(filename), std::move(fp), true,
|
|
|
|
|
UsesLowerLeftOrigin(), width, height, std::move(pixels), pixels_stride, pixels_format);
|
|
|
|
|
compress_thread.detach();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::unique_ptr<GPUDevice> GPUDevice::CreateDeviceForAPI(RenderAPI api)
|
|
|
|
|
{
|
|
|
|
|
switch (api)
|
|
|
|
|
|