GPU: Add display rotation option
This commit is contained in:
@@ -4393,6 +4393,10 @@ void FullscreenUI::DrawDisplaySettingsPage()
|
||||
&Settings::GetDisplayAlignmentName, &Settings::GetDisplayAlignmentDisplayName,
|
||||
DisplayAlignment::Count);
|
||||
|
||||
DrawEnumSetting(bsi, FSUI_CSTR("Screen Rotation"), FSUI_CSTR("Determines the rotation of the simulated TV screen."),
|
||||
"Display", "Rotation", Settings::DEFAULT_DISPLAY_ROTATION, &Settings::ParseDisplayRotation,
|
||||
&Settings::GetDisplayRotationName, &Settings::GetDisplayRotationDisplayName, DisplayRotation::Count);
|
||||
|
||||
if (is_hardware)
|
||||
{
|
||||
DrawEnumSetting(bsi, FSUI_CSTR("Line Detection"),
|
||||
@@ -7382,6 +7386,7 @@ TRANSLATE_NOOP("FullscreenUI", "Determines the amount of audio buffered before b
|
||||
TRANSLATE_NOOP("FullscreenUI", "Determines the emulated hardware type.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Determines the format that screenshots will be saved/compressed with.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Determines the position on the screen when black borders must be added.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Determines the rotation of the simulated TV screen.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Determines the size of screenshots created by DuckStation.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Determines which algorithm is used to convert interlaced frames to progressive for display on your system.");
|
||||
@@ -7690,6 +7695,7 @@ TRANSLATE_NOOP("FullscreenUI", "Scaling");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Scan For New Games");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Scanning Subdirectories");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Screen Position");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Screen Rotation");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Screenshot Format");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Screenshot Quality");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Screenshot Size");
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "fmt/format.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <numbers>
|
||||
#include <thread>
|
||||
|
||||
Log_SetChannel(GPU);
|
||||
@@ -138,7 +139,8 @@ void GPU::UpdateSettings(const Settings& old_settings)
|
||||
|
||||
if (!CompileDisplayPipelines(g_settings.display_scaling != old_settings.display_scaling,
|
||||
g_settings.display_deinterlacing_mode != old_settings.display_deinterlacing_mode,
|
||||
g_settings.display_24bit_chroma_smoothing != old_settings.display_24bit_chroma_smoothing))
|
||||
g_settings.display_24bit_chroma_smoothing !=
|
||||
old_settings.display_24bit_chroma_smoothing))
|
||||
{
|
||||
Panic("Failed to compile display pipeline on settings change.");
|
||||
}
|
||||
@@ -1094,7 +1096,8 @@ void GPU::UpdateCommandTickEvent()
|
||||
void GPU::ConvertScreenCoordinatesToDisplayCoordinates(float window_x, float window_y, float* display_x,
|
||||
float* display_y) const
|
||||
{
|
||||
const GSVector4i draw_rc = CalculateDrawRect(g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(), true);
|
||||
const GSVector4i draw_rc =
|
||||
CalculateDrawRect(g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(), true, true);
|
||||
|
||||
// convert coordinates to active display region, then to full display region
|
||||
const float scaled_display_x = (window_x - static_cast<float>(draw_rc.left)) / static_cast<float>(draw_rc.width());
|
||||
@@ -1104,6 +1107,8 @@ void GPU::ConvertScreenCoordinatesToDisplayCoordinates(float window_x, float win
|
||||
*display_x = scaled_display_x * static_cast<float>(m_crtc_state.display_width);
|
||||
*display_y = scaled_display_y * static_cast<float>(m_crtc_state.display_height);
|
||||
|
||||
// TODO: apply rotation matrix
|
||||
|
||||
DEV_LOG("win {:.0f},{:.0f} -> local {:.0f},{:.0f}, disp {:.2f},{:.2f} (size {},{} frac {},{})", window_x, window_y,
|
||||
window_x - draw_rc.left, window_y - draw_rc.top, *display_x, *display_y, m_crtc_state.display_width,
|
||||
m_crtc_state.display_height, *display_x / static_cast<float>(m_crtc_state.display_width),
|
||||
@@ -1936,7 +1941,8 @@ bool GPU::PresentDisplay()
|
||||
{
|
||||
FlushRender();
|
||||
|
||||
const GSVector4i draw_rect = CalculateDrawRect(g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight());
|
||||
const GSVector4i draw_rect = CalculateDrawRect(g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(),
|
||||
!g_settings.debugging.show_vram, true);
|
||||
return RenderDisplay(nullptr, draw_rect, !g_settings.debugging.show_vram);
|
||||
}
|
||||
|
||||
@@ -2007,6 +2013,7 @@ bool GPU::RenderDisplay(GPUTexture* target, const GSVector4i draw_rect, bool pos
|
||||
float src_size[4];
|
||||
float clamp_rect[4];
|
||||
float params[4];
|
||||
float rotation_matrix[2][2];
|
||||
} uniforms;
|
||||
std::memset(uniforms.params, 0, sizeof(uniforms.params));
|
||||
|
||||
@@ -2060,6 +2067,23 @@ bool GPU::RenderDisplay(GPUTexture* target, const GSVector4i draw_rect, bool pos
|
||||
uniforms.src_size[1] = static_cast<float>(display_texture->GetHeight());
|
||||
uniforms.src_size[2] = rcp_width;
|
||||
uniforms.src_size[3] = rcp_height;
|
||||
|
||||
if (g_settings.display_rotation != DisplayRotation::Normal)
|
||||
{
|
||||
static constexpr const std::array<float, static_cast<size_t>(DisplayRotation::Count) - 1> rotation_radians = {{
|
||||
static_cast<float>(std::numbers::pi * 1.5f), // Rotate90
|
||||
static_cast<float>(std::numbers::pi), // Rotate180
|
||||
static_cast<float>(std::numbers::pi / 2.0), // Rotate270
|
||||
}};
|
||||
|
||||
GSMatrix2x2::Rotation(rotation_radians[static_cast<size_t>(g_settings.display_rotation) - 1])
|
||||
.store(uniforms.rotation_matrix);
|
||||
}
|
||||
else
|
||||
{
|
||||
GSMatrix2x2::Identity().store(uniforms.rotation_matrix);
|
||||
}
|
||||
|
||||
g_gpu_device->PushUniformBuffer(&uniforms, sizeof(uniforms));
|
||||
|
||||
g_gpu_device->SetViewportAndScissor(real_draw_rect);
|
||||
@@ -2315,7 +2339,8 @@ bool GPU::ApplyChromaSmoothing()
|
||||
return true;
|
||||
}
|
||||
|
||||
GSVector4i GPU::CalculateDrawRect(s32 window_width, s32 window_height, bool apply_aspect_ratio /* = true */) const
|
||||
GSVector4i GPU::CalculateDrawRect(s32 window_width, s32 window_height, bool apply_rotation,
|
||||
bool apply_aspect_ratio) const
|
||||
{
|
||||
const bool integer_scale = (g_settings.display_scaling == DisplayScalingMode::NearestInteger ||
|
||||
g_settings.display_scaling == DisplayScalingMode::BlinearInteger);
|
||||
@@ -2347,6 +2372,15 @@ GSVector4i GPU::CalculateDrawRect(s32 window_width, s32 window_height, bool appl
|
||||
active_height /= x_scale;
|
||||
}
|
||||
|
||||
// swap width/height when rotated, the flipping of padding is taken care of in the shader with the rotation matrix
|
||||
if (g_settings.display_rotation == DisplayRotation::Rotate90 ||
|
||||
g_settings.display_rotation == DisplayRotation::Rotate270)
|
||||
{
|
||||
std::swap(display_width, display_height);
|
||||
std::swap(active_width, active_height);
|
||||
std::swap(active_top, active_left);
|
||||
}
|
||||
|
||||
// now fit it within the window
|
||||
float scale;
|
||||
float left_padding, top_padding;
|
||||
@@ -2640,7 +2674,7 @@ bool GPU::RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mod
|
||||
{
|
||||
u32 width = g_gpu_device->GetWindowWidth();
|
||||
u32 height = g_gpu_device->GetWindowHeight();
|
||||
GSVector4i draw_rect = CalculateDrawRect(width, height, true);
|
||||
GSVector4i draw_rect = CalculateDrawRect(width, height, true, !g_settings.debugging.show_vram);
|
||||
|
||||
const bool internal_resolution = (mode != DisplayScreenshotMode::ScreenResolution || g_settings.debugging.show_vram);
|
||||
if (internal_resolution && m_display_texture_view_width != 0 && m_display_texture_view_height != 0)
|
||||
|
||||
@@ -207,7 +207,7 @@ public:
|
||||
virtual void FlushRender() = 0;
|
||||
|
||||
/// Helper function for computing the draw rectangle in a larger window.
|
||||
GSVector4i CalculateDrawRect(s32 window_width, s32 window_height, bool apply_aspect_ratio = true) const;
|
||||
GSVector4i CalculateDrawRect(s32 window_width, s32 window_height, bool apply_rotation, bool apply_aspect_ratio) const;
|
||||
|
||||
/// Helper function to save current display texture to PNG.
|
||||
bool WriteDisplayTextureToFile(std::string filename, bool compress_on_thread = false);
|
||||
|
||||
@@ -12,7 +12,11 @@ GPUShaderGen::~GPUShaderGen() = default;
|
||||
|
||||
void GPUShaderGen::WriteDisplayUniformBuffer(std::stringstream& ss)
|
||||
{
|
||||
DeclareUniformBuffer(ss, {"float4 u_src_rect", "float4 u_src_size", "float4 u_clamp_rect", "float4 u_params"}, true);
|
||||
// Rotation matrix split into rows to avoid padding in HLSL.
|
||||
DeclareUniformBuffer(ss,
|
||||
{"float4 u_src_rect", "float4 u_src_size", "float4 u_clamp_rect", "float4 u_params",
|
||||
"float2 u_rotation_matrix0", "float2 u_rotation_matrix1"},
|
||||
true);
|
||||
|
||||
ss << R"(
|
||||
float2 ClampUV(float2 uv) {
|
||||
@@ -31,6 +35,10 @@ std::string GPUShaderGen::GenerateDisplayVertexShader()
|
||||
float2 pos = float2(float((v_id << 1) & 2u), float(v_id & 2u));
|
||||
v_tex0 = u_src_rect.xy + pos * u_src_rect.zw;
|
||||
v_pos = float4(pos * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);
|
||||
|
||||
// Avoid HLSL/GLSL constructor differences by explicitly multiplying the matrix.
|
||||
v_pos.xy = float2(dot(u_rotation_matrix0, v_pos.xy), dot(u_rotation_matrix1, v_pos.xy));
|
||||
|
||||
#if API_VULKAN
|
||||
v_pos.y = -v_pos.y;
|
||||
#endif
|
||||
|
||||
@@ -409,7 +409,7 @@ DEFINE_HOTKEY("TogglePostProcessing", TRANSLATE_NOOP("Hotkeys", "Graphics"),
|
||||
PostProcessing::DisplayChain.Toggle();
|
||||
})
|
||||
|
||||
DEFINE_HOTKEY("ToggleInternalPostProcessing", TRANSLATE_NOOP("Hotkeys", "Graphics"),
|
||||
DEFINE_HOTKEY("ToggleInternalPostProcessing", TRANSLATE_NOOP("Hotkeys", "Graphics"),
|
||||
TRANSLATE_NOOP("Hotkeys", "Toggle Internal Post-Processing"), [](s32 pressed) {
|
||||
if (!pressed && System::IsValid())
|
||||
PostProcessing::InternalChain.Toggle();
|
||||
@@ -494,6 +494,27 @@ DEFINE_HOTKEY("ToggleOSD", TRANSLATE_NOOP("Hotkeys", "Graphics"), TRANSLATE_NOOP
|
||||
HotkeyToggleOSD();
|
||||
})
|
||||
|
||||
DEFINE_HOTKEY("RotateClockwise", TRANSLATE_NOOP("Hotkeys", "Graphics"),
|
||||
TRANSLATE_NOOP("Hotkeys", "Rotate Display Clockwise"), [](s32 pressed) {
|
||||
if (!pressed)
|
||||
{
|
||||
g_settings.display_rotation = static_cast<DisplayRotation>(
|
||||
(static_cast<u8>(g_settings.display_rotation) + 1) % static_cast<u8>(DisplayRotation::Count));
|
||||
}
|
||||
})
|
||||
|
||||
DEFINE_HOTKEY("RotateCounterclockwise", TRANSLATE_NOOP("Hotkeys", "Graphics"),
|
||||
TRANSLATE_NOOP("Hotkeys", "Rotate Display Counterclockwise"), [](s32 pressed) {
|
||||
if (!pressed)
|
||||
{
|
||||
g_settings.display_rotation =
|
||||
(g_settings.display_rotation > static_cast<DisplayRotation>(0)) ?
|
||||
static_cast<DisplayRotation>((static_cast<u8>(g_settings.display_rotation) - 1) %
|
||||
static_cast<u8>(DisplayRotation::Count)) :
|
||||
static_cast<DisplayRotation>(static_cast<u8>(DisplayRotation::Count) - 1);
|
||||
}
|
||||
})
|
||||
|
||||
DEFINE_HOTKEY("AudioMute", TRANSLATE_NOOP("Hotkeys", "Audio"), TRANSLATE_NOOP("Hotkeys", "Toggle Mute"),
|
||||
[](s32 pressed) {
|
||||
if (!pressed && System::IsValid())
|
||||
|
||||
@@ -259,6 +259,10 @@ void Settings::Load(SettingsInterface& si)
|
||||
ParseDisplayAlignment(
|
||||
si.GetStringValue("Display", "Alignment", GetDisplayAlignmentName(DEFAULT_DISPLAY_ALIGNMENT)).c_str())
|
||||
.value_or(DEFAULT_DISPLAY_ALIGNMENT);
|
||||
display_rotation =
|
||||
ParseDisplayRotation(
|
||||
si.GetStringValue("Display", "Rotation", GetDisplayRotationName(DEFAULT_DISPLAY_ROTATION)).c_str())
|
||||
.value_or(DEFAULT_DISPLAY_ROTATION);
|
||||
display_scaling =
|
||||
ParseDisplayScaling(si.GetStringValue("Display", "Scaling", GetDisplayScalingName(DEFAULT_DISPLAY_SCALING)).c_str())
|
||||
.value_or(DEFAULT_DISPLAY_SCALING);
|
||||
@@ -541,6 +545,7 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
|
||||
si.SetBoolValue("Display", "Force4_3For24Bit", display_force_4_3_for_24bit);
|
||||
si.SetStringValue("Display", "AspectRatio", GetDisplayAspectRatioName(display_aspect_ratio));
|
||||
si.SetStringValue("Display", "Alignment", GetDisplayAlignmentName(display_alignment));
|
||||
si.SetStringValue("Display", "Rotation", GetDisplayRotationName(display_rotation));
|
||||
si.SetStringValue("Display", "Scaling", GetDisplayScalingName(display_scaling));
|
||||
si.SetBoolValue("Display", "OptimalFramePacing", display_optimal_frame_pacing);
|
||||
si.SetBoolValue("Display", "PreFrameSleep", display_pre_frame_sleep);
|
||||
@@ -1456,6 +1461,38 @@ const char* Settings::GetDisplayAlignmentDisplayName(DisplayAlignment alignment)
|
||||
return Host::TranslateToCString("DisplayAlignment", s_display_alignment_display_names[static_cast<int>(alignment)]);
|
||||
}
|
||||
|
||||
static constexpr const std::array s_display_rotation_names = {"Normal", "Rotate90", "Rotate180", "Rotate270"};
|
||||
static constexpr const std::array s_display_rotation_display_names = {
|
||||
TRANSLATE_NOOP("Settings", "No Rotation"),
|
||||
TRANSLATE_NOOP("Settings", "Rotate 90° (Clockwise)"),
|
||||
TRANSLATE_NOOP("Settings", "Rotate 180° (Vertical Flip)"),
|
||||
TRANSLATE_NOOP("Settings", "Rotate 270° (Clockwise)"),
|
||||
};
|
||||
|
||||
std::optional<DisplayRotation> Settings::ParseDisplayRotation(const char* str)
|
||||
{
|
||||
int index = 0;
|
||||
for (const char* name : s_display_rotation_names)
|
||||
{
|
||||
if (StringUtil::Strcasecmp(name, str) == 0)
|
||||
return static_cast<DisplayRotation>(index);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const char* Settings::GetDisplayRotationName(DisplayRotation rotation)
|
||||
{
|
||||
return s_display_rotation_names[static_cast<int>(rotation)];
|
||||
}
|
||||
|
||||
const char* Settings::GetDisplayRotationDisplayName(DisplayRotation rotation)
|
||||
{
|
||||
return Host::TranslateToCString("Settings", s_display_rotation_display_names[static_cast<size_t>(rotation)]);
|
||||
}
|
||||
|
||||
static constexpr const std::array s_display_scaling_names = {
|
||||
"Nearest", "NearestInteger", "BilinearSmooth", "BilinearSharp", "BilinearInteger",
|
||||
};
|
||||
|
||||
@@ -143,6 +143,7 @@ struct Settings
|
||||
DisplayCropMode display_crop_mode = DEFAULT_DISPLAY_CROP_MODE;
|
||||
DisplayAspectRatio display_aspect_ratio = DEFAULT_DISPLAY_ASPECT_RATIO;
|
||||
DisplayAlignment display_alignment = DEFAULT_DISPLAY_ALIGNMENT;
|
||||
DisplayRotation display_rotation = DEFAULT_DISPLAY_ROTATION;
|
||||
DisplayScalingMode display_scaling = DEFAULT_DISPLAY_SCALING;
|
||||
DisplayExclusiveFullscreenControl display_exclusive_fullscreen_control = DEFAULT_DISPLAY_EXCLUSIVE_FULLSCREEN_CONTROL;
|
||||
DisplayScreenshotMode display_screenshot_mode = DEFAULT_DISPLAY_SCREENSHOT_MODE;
|
||||
@@ -424,6 +425,10 @@ struct Settings
|
||||
static const char* GetDisplayAlignmentName(DisplayAlignment alignment);
|
||||
static const char* GetDisplayAlignmentDisplayName(DisplayAlignment alignment);
|
||||
|
||||
static std::optional<DisplayRotation> ParseDisplayRotation(const char* str);
|
||||
static const char* GetDisplayRotationName(DisplayRotation alignment);
|
||||
static const char* GetDisplayRotationDisplayName(DisplayRotation alignment);
|
||||
|
||||
static std::optional<DisplayScalingMode> ParseDisplayScaling(const char* str);
|
||||
static const char* GetDisplayScalingName(DisplayScalingMode mode);
|
||||
static const char* GetDisplayScalingDisplayName(DisplayScalingMode mode);
|
||||
@@ -480,6 +485,7 @@ struct Settings
|
||||
static constexpr DisplayCropMode DEFAULT_DISPLAY_CROP_MODE = DisplayCropMode::Overscan;
|
||||
static constexpr DisplayAspectRatio DEFAULT_DISPLAY_ASPECT_RATIO = DisplayAspectRatio::Auto;
|
||||
static constexpr DisplayAlignment DEFAULT_DISPLAY_ALIGNMENT = DisplayAlignment::Center;
|
||||
static constexpr DisplayRotation DEFAULT_DISPLAY_ROTATION = DisplayRotation::Normal;
|
||||
static constexpr DisplayScalingMode DEFAULT_DISPLAY_SCALING = DisplayScalingMode::BilinearSmooth;
|
||||
static constexpr DisplayExclusiveFullscreenControl DEFAULT_DISPLAY_EXCLUSIVE_FULLSCREEN_CONTROL =
|
||||
DisplayExclusiveFullscreenControl::Automatic;
|
||||
|
||||
@@ -4065,7 +4065,6 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
|
||||
g_settings.display_24bit_chroma_smoothing != old_settings.display_24bit_chroma_smoothing ||
|
||||
g_settings.display_crop_mode != old_settings.display_crop_mode ||
|
||||
g_settings.display_aspect_ratio != old_settings.display_aspect_ratio ||
|
||||
g_settings.display_alignment != old_settings.display_alignment ||
|
||||
g_settings.display_scaling != old_settings.display_scaling ||
|
||||
g_settings.display_show_gpu_usage != old_settings.display_show_gpu_usage ||
|
||||
g_settings.gpu_pgxp_enable != old_settings.gpu_pgxp_enable ||
|
||||
@@ -5304,11 +5303,14 @@ void System::RequestDisplaySize(float scale /*= 0.0f*/)
|
||||
(static_cast<float>(g_gpu->GetCRTCDisplayWidth()) / static_cast<float>(g_gpu->GetCRTCDisplayHeight())) /
|
||||
g_gpu->ComputeDisplayAspectRatio();
|
||||
|
||||
const u32 requested_width =
|
||||
u32 requested_width =
|
||||
std::max<u32>(static_cast<u32>(std::ceil(static_cast<float>(g_gpu->GetCRTCDisplayWidth()) * scale)), 1);
|
||||
const u32 requested_height =
|
||||
u32 requested_height =
|
||||
std::max<u32>(static_cast<u32>(std::ceil(static_cast<float>(g_gpu->GetCRTCDisplayHeight()) * y_scale * scale)), 1);
|
||||
|
||||
if (g_settings.display_rotation == DisplayRotation::Rotate90 || g_settings.display_rotation == DisplayRotation::Rotate180)
|
||||
std::swap(requested_width, requested_height);
|
||||
|
||||
Host::RequestResizeHostDisplay(static_cast<s32>(requested_width), static_cast<s32>(requested_height));
|
||||
}
|
||||
|
||||
|
||||
@@ -152,6 +152,15 @@ enum class DisplayAlignment : u8
|
||||
Count
|
||||
};
|
||||
|
||||
enum class DisplayRotation : u8
|
||||
{
|
||||
Normal,
|
||||
Rotate90,
|
||||
Rotate180,
|
||||
Rotate270,
|
||||
Count
|
||||
};
|
||||
|
||||
enum class DisplayScalingMode : u8
|
||||
{
|
||||
Nearest,
|
||||
|
||||
Reference in New Issue
Block a user