GPU: Add display rotation option

This commit is contained in:
Stenzek 2024-07-26 22:45:23 +10:00
parent 5656f91bd2
commit 7a3a72ec3d
No known key found for this signature in database
20 changed files with 252 additions and 15 deletions

View file

@ -21,6 +21,7 @@ add_library(common
fifo_queue.h fifo_queue.h
file_system.cpp file_system.cpp
file_system.h file_system.h
gsvector.cpp
gsvector.h gsvector.h
gsvector_formatter.h gsvector_formatter.h
gsvector_neon.h gsvector_neon.h

View file

@ -56,6 +56,7 @@
<ClCompile Include="error.cpp" /> <ClCompile Include="error.cpp" />
<ClCompile Include="fastjmp.cpp" /> <ClCompile Include="fastjmp.cpp" />
<ClCompile Include="file_system.cpp" /> <ClCompile Include="file_system.cpp" />
<ClCompile Include="gsvector.cpp" />
<ClCompile Include="layered_settings_interface.cpp" /> <ClCompile Include="layered_settings_interface.cpp" />
<ClCompile Include="log.cpp" /> <ClCompile Include="log.cpp" />
<ClCompile Include="memmap.cpp" /> <ClCompile Include="memmap.cpp" />

View file

@ -78,6 +78,7 @@
</ClCompile> </ClCompile>
<ClCompile Include="dynamic_library.cpp" /> <ClCompile Include="dynamic_library.cpp" />
<ClCompile Include="binary_span_reader_writer.cpp" /> <ClCompile Include="binary_span_reader_writer.cpp" />
<ClCompile Include="gsvector.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Natvis Include="bitfield.natvis" /> <Natvis Include="bitfield.natvis" />

67
src/common/gsvector.cpp Normal file
View file

@ -0,0 +1,67 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "gsvector.h"
#include <cmath>
GSMatrix2x2::GSMatrix2x2(float e00, float e01, float e10, float e11)
{
E[0][0] = e00;
E[0][1] = e01;
E[1][0] = e10;
E[1][1] = e11;
}
GSMatrix2x2 GSMatrix2x2::operator*(const GSMatrix2x2& m) const
{
GSMatrix2x2 ret;
ret.E[0][0] = E[0][0] * m.E[0][0] + E[0][1] * m.E[1][0];
ret.E[0][1] = E[0][0] * m.E[0][1] + E[0][1] * m.E[1][1];
ret.E[1][0] = E[1][0] * m.E[0][0] + E[1][1] * m.E[1][0];
ret.E[1][1] = E[1][0] * m.E[0][1] + E[1][1] * m.E[1][1];
return ret;
}
GSVector2 GSMatrix2x2::operator*(const GSVector2& v) const
{
return GSVector2(row(0).dot(v), row(1).dot(v));
}
GSMatrix2x2 GSMatrix2x2::Identity()
{
GSMatrix2x2 ret;
ret.E[0][0] = 1.0f;
ret.E[0][1] = 0.0f;
ret.E[1][0] = 0.0f;
ret.E[1][1] = 1.0f;
return ret;
}
GSMatrix2x2 GSMatrix2x2::Rotation(float angle_in_radians)
{
const float sin_angle = std::sin(angle_in_radians);
const float cos_angle = std::cos(angle_in_radians);
GSMatrix2x2 ret;
ret.E[0][0] = cos_angle;
ret.E[0][1] = -sin_angle;
ret.E[1][0] = sin_angle;
ret.E[1][1] = cos_angle;
return ret;
}
GSVector2 GSMatrix2x2::row(size_t i) const
{
return GSVector2::load(&E[i][0]);
}
GSVector2 GSMatrix2x2::col(size_t i) const
{
return GSVector2(E[0][i], E[1][i]);
}
void GSMatrix2x2::store(void* m)
{
std::memcpy(m, E, sizeof(E));
}

View file

@ -12,3 +12,24 @@
#else #else
#include "common/gsvector_nosimd.h" #include "common/gsvector_nosimd.h"
#endif #endif
class GSMatrix2x2
{
public:
GSMatrix2x2() = default;
GSMatrix2x2(float e00, float e01, float e10, float e11);
GSMatrix2x2 operator*(const GSMatrix2x2& m) const;
GSVector2 operator*(const GSVector2& v) const;
static GSMatrix2x2 Identity();
static GSMatrix2x2 Rotation(float angle_in_radians);
GSVector2 row(size_t i) const;
GSVector2 col(size_t i) const;
void store(void* m);
float E[2][2];
};

View file

@ -800,6 +800,8 @@ public:
return vget_lane_s32(vreinterpret_s32_f32(v2s), i); return vget_lane_s32(vreinterpret_s32_f32(v2s), i);
} }
ALWAYS_INLINE float dot(const GSVector2& v) const { return vaddv_f32(vmul_f32(v2s, v.v2s)); }
ALWAYS_INLINE static GSVector2 zero() { return GSVector2(vdup_n_f32(0.0f)); } ALWAYS_INLINE static GSVector2 zero() { return GSVector2(vdup_n_f32(0.0f)); }
ALWAYS_INLINE static GSVector2 xffffffff() { return GSVector2(vreinterpret_f32_u32(vdup_n_u32(0xFFFFFFFFu))); } ALWAYS_INLINE static GSVector2 xffffffff() { return GSVector2(vreinterpret_f32_u32(vdup_n_u32(0xFFFFFFFFu))); }

View file

@ -666,6 +666,8 @@ public:
return I32[i]; return I32[i];
} }
ALWAYS_INLINE float dot(const GSVector2& v) const { return (x * v.x + y * v.y); }
ALWAYS_INLINE static constexpr GSVector2 zero() { return GSVector2::cxpr(0.0f, 0.0f); } ALWAYS_INLINE static constexpr GSVector2 zero() { return GSVector2::cxpr(0.0f, 0.0f); }
ALWAYS_INLINE static constexpr GSVector2 xffffffff() ALWAYS_INLINE static constexpr GSVector2 xffffffff()

View file

@ -643,6 +643,8 @@ public:
return _mm_extract_ps(m, i); return _mm_extract_ps(m, i);
} }
ALWAYS_INLINE float dot(const GSVector2& v) const { return _mm_cvtss_f32(_mm_dp_ps(m, v.m, 0x31)); }
ALWAYS_INLINE static GSVector2 zero() { return GSVector2(_mm_setzero_ps()); } ALWAYS_INLINE static GSVector2 zero() { return GSVector2(_mm_setzero_ps()); }
ALWAYS_INLINE static GSVector2 xffffffff() { return zero() == zero(); } ALWAYS_INLINE static GSVector2 xffffffff() { return zero() == zero(); }

View file

@ -4393,6 +4393,10 @@ void FullscreenUI::DrawDisplaySettingsPage()
&Settings::GetDisplayAlignmentName, &Settings::GetDisplayAlignmentDisplayName, &Settings::GetDisplayAlignmentName, &Settings::GetDisplayAlignmentDisplayName,
DisplayAlignment::Count); 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) if (is_hardware)
{ {
DrawEnumSetting(bsi, FSUI_CSTR("Line Detection"), 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 emulated hardware type.");
TRANSLATE_NOOP("FullscreenUI", "Determines the format that screenshots will be saved/compressed with."); 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 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 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 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."); 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", "Scan For New Games");
TRANSLATE_NOOP("FullscreenUI", "Scanning Subdirectories"); TRANSLATE_NOOP("FullscreenUI", "Scanning Subdirectories");
TRANSLATE_NOOP("FullscreenUI", "Screen Position"); TRANSLATE_NOOP("FullscreenUI", "Screen Position");
TRANSLATE_NOOP("FullscreenUI", "Screen Rotation");
TRANSLATE_NOOP("FullscreenUI", "Screenshot Format"); TRANSLATE_NOOP("FullscreenUI", "Screenshot Format");
TRANSLATE_NOOP("FullscreenUI", "Screenshot Quality"); TRANSLATE_NOOP("FullscreenUI", "Screenshot Quality");
TRANSLATE_NOOP("FullscreenUI", "Screenshot Size"); TRANSLATE_NOOP("FullscreenUI", "Screenshot Size");

View file

@ -32,6 +32,7 @@
#include "fmt/format.h" #include "fmt/format.h"
#include <cmath> #include <cmath>
#include <numbers>
#include <thread> #include <thread>
Log_SetChannel(GPU); Log_SetChannel(GPU);
@ -138,7 +139,8 @@ void GPU::UpdateSettings(const Settings& old_settings)
if (!CompileDisplayPipelines(g_settings.display_scaling != old_settings.display_scaling, if (!CompileDisplayPipelines(g_settings.display_scaling != old_settings.display_scaling,
g_settings.display_deinterlacing_mode != old_settings.display_deinterlacing_mode, 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."); 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, void GPU::ConvertScreenCoordinatesToDisplayCoordinates(float window_x, float window_y, float* display_x,
float* display_y) const 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 // 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()); 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_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); *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, 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, 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), m_crtc_state.display_height, *display_x / static_cast<float>(m_crtc_state.display_width),
@ -1936,7 +1941,8 @@ bool GPU::PresentDisplay()
{ {
FlushRender(); 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); 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 src_size[4];
float clamp_rect[4]; float clamp_rect[4];
float params[4]; float params[4];
float rotation_matrix[2][2];
} uniforms; } uniforms;
std::memset(uniforms.params, 0, sizeof(uniforms.params)); 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[1] = static_cast<float>(display_texture->GetHeight());
uniforms.src_size[2] = rcp_width; uniforms.src_size[2] = rcp_width;
uniforms.src_size[3] = rcp_height; 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->PushUniformBuffer(&uniforms, sizeof(uniforms));
g_gpu_device->SetViewportAndScissor(real_draw_rect); g_gpu_device->SetViewportAndScissor(real_draw_rect);
@ -2315,7 +2339,8 @@ bool GPU::ApplyChromaSmoothing()
return true; 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 || const bool integer_scale = (g_settings.display_scaling == DisplayScalingMode::NearestInteger ||
g_settings.display_scaling == DisplayScalingMode::BlinearInteger); 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; 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 // now fit it within the window
float scale; float scale;
float left_padding, top_padding; float left_padding, top_padding;
@ -2640,7 +2674,7 @@ bool GPU::RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mod
{ {
u32 width = g_gpu_device->GetWindowWidth(); u32 width = g_gpu_device->GetWindowWidth();
u32 height = g_gpu_device->GetWindowHeight(); 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); 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) if (internal_resolution && m_display_texture_view_width != 0 && m_display_texture_view_height != 0)

View file

@ -207,7 +207,7 @@ public:
virtual void FlushRender() = 0; virtual void FlushRender() = 0;
/// Helper function for computing the draw rectangle in a larger window. /// 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. /// Helper function to save current display texture to PNG.
bool WriteDisplayTextureToFile(std::string filename, bool compress_on_thread = false); bool WriteDisplayTextureToFile(std::string filename, bool compress_on_thread = false);

View file

@ -12,7 +12,11 @@ GPUShaderGen::~GPUShaderGen() = default;
void GPUShaderGen::WriteDisplayUniformBuffer(std::stringstream& ss) 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"( ss << R"(
float2 ClampUV(float2 uv) { float2 ClampUV(float2 uv) {
@ -31,6 +35,10 @@ std::string GPUShaderGen::GenerateDisplayVertexShader()
float2 pos = float2(float((v_id << 1) & 2u), float(v_id & 2u)); float2 pos = float2(float((v_id << 1) & 2u), float(v_id & 2u));
v_tex0 = u_src_rect.xy + pos * u_src_rect.zw; 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); 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 #if API_VULKAN
v_pos.y = -v_pos.y; v_pos.y = -v_pos.y;
#endif #endif

View file

@ -409,7 +409,7 @@ DEFINE_HOTKEY("TogglePostProcessing", TRANSLATE_NOOP("Hotkeys", "Graphics"),
PostProcessing::DisplayChain.Toggle(); 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) { TRANSLATE_NOOP("Hotkeys", "Toggle Internal Post-Processing"), [](s32 pressed) {
if (!pressed && System::IsValid()) if (!pressed && System::IsValid())
PostProcessing::InternalChain.Toggle(); PostProcessing::InternalChain.Toggle();
@ -494,6 +494,27 @@ DEFINE_HOTKEY("ToggleOSD", TRANSLATE_NOOP("Hotkeys", "Graphics"), TRANSLATE_NOOP
HotkeyToggleOSD(); 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"), DEFINE_HOTKEY("AudioMute", TRANSLATE_NOOP("Hotkeys", "Audio"), TRANSLATE_NOOP("Hotkeys", "Toggle Mute"),
[](s32 pressed) { [](s32 pressed) {
if (!pressed && System::IsValid()) if (!pressed && System::IsValid())

View file

@ -259,6 +259,10 @@ void Settings::Load(SettingsInterface& si)
ParseDisplayAlignment( ParseDisplayAlignment(
si.GetStringValue("Display", "Alignment", GetDisplayAlignmentName(DEFAULT_DISPLAY_ALIGNMENT)).c_str()) si.GetStringValue("Display", "Alignment", GetDisplayAlignmentName(DEFAULT_DISPLAY_ALIGNMENT)).c_str())
.value_or(DEFAULT_DISPLAY_ALIGNMENT); .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 = display_scaling =
ParseDisplayScaling(si.GetStringValue("Display", "Scaling", GetDisplayScalingName(DEFAULT_DISPLAY_SCALING)).c_str()) ParseDisplayScaling(si.GetStringValue("Display", "Scaling", GetDisplayScalingName(DEFAULT_DISPLAY_SCALING)).c_str())
.value_or(DEFAULT_DISPLAY_SCALING); .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.SetBoolValue("Display", "Force4_3For24Bit", display_force_4_3_for_24bit);
si.SetStringValue("Display", "AspectRatio", GetDisplayAspectRatioName(display_aspect_ratio)); si.SetStringValue("Display", "AspectRatio", GetDisplayAspectRatioName(display_aspect_ratio));
si.SetStringValue("Display", "Alignment", GetDisplayAlignmentName(display_alignment)); si.SetStringValue("Display", "Alignment", GetDisplayAlignmentName(display_alignment));
si.SetStringValue("Display", "Rotation", GetDisplayRotationName(display_rotation));
si.SetStringValue("Display", "Scaling", GetDisplayScalingName(display_scaling)); si.SetStringValue("Display", "Scaling", GetDisplayScalingName(display_scaling));
si.SetBoolValue("Display", "OptimalFramePacing", display_optimal_frame_pacing); si.SetBoolValue("Display", "OptimalFramePacing", display_optimal_frame_pacing);
si.SetBoolValue("Display", "PreFrameSleep", display_pre_frame_sleep); 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)]); 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 = { static constexpr const std::array s_display_scaling_names = {
"Nearest", "NearestInteger", "BilinearSmooth", "BilinearSharp", "BilinearInteger", "Nearest", "NearestInteger", "BilinearSmooth", "BilinearSharp", "BilinearInteger",
}; };

View file

@ -143,6 +143,7 @@ struct Settings
DisplayCropMode display_crop_mode = DEFAULT_DISPLAY_CROP_MODE; DisplayCropMode display_crop_mode = DEFAULT_DISPLAY_CROP_MODE;
DisplayAspectRatio display_aspect_ratio = DEFAULT_DISPLAY_ASPECT_RATIO; DisplayAspectRatio display_aspect_ratio = DEFAULT_DISPLAY_ASPECT_RATIO;
DisplayAlignment display_alignment = DEFAULT_DISPLAY_ALIGNMENT; DisplayAlignment display_alignment = DEFAULT_DISPLAY_ALIGNMENT;
DisplayRotation display_rotation = DEFAULT_DISPLAY_ROTATION;
DisplayScalingMode display_scaling = DEFAULT_DISPLAY_SCALING; DisplayScalingMode display_scaling = DEFAULT_DISPLAY_SCALING;
DisplayExclusiveFullscreenControl display_exclusive_fullscreen_control = DEFAULT_DISPLAY_EXCLUSIVE_FULLSCREEN_CONTROL; DisplayExclusiveFullscreenControl display_exclusive_fullscreen_control = DEFAULT_DISPLAY_EXCLUSIVE_FULLSCREEN_CONTROL;
DisplayScreenshotMode display_screenshot_mode = DEFAULT_DISPLAY_SCREENSHOT_MODE; DisplayScreenshotMode display_screenshot_mode = DEFAULT_DISPLAY_SCREENSHOT_MODE;
@ -424,6 +425,10 @@ struct Settings
static const char* GetDisplayAlignmentName(DisplayAlignment alignment); static const char* GetDisplayAlignmentName(DisplayAlignment alignment);
static const char* GetDisplayAlignmentDisplayName(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 std::optional<DisplayScalingMode> ParseDisplayScaling(const char* str);
static const char* GetDisplayScalingName(DisplayScalingMode mode); static const char* GetDisplayScalingName(DisplayScalingMode mode);
static const char* GetDisplayScalingDisplayName(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 DisplayCropMode DEFAULT_DISPLAY_CROP_MODE = DisplayCropMode::Overscan;
static constexpr DisplayAspectRatio DEFAULT_DISPLAY_ASPECT_RATIO = DisplayAspectRatio::Auto; static constexpr DisplayAspectRatio DEFAULT_DISPLAY_ASPECT_RATIO = DisplayAspectRatio::Auto;
static constexpr DisplayAlignment DEFAULT_DISPLAY_ALIGNMENT = DisplayAlignment::Center; 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 DisplayScalingMode DEFAULT_DISPLAY_SCALING = DisplayScalingMode::BilinearSmooth;
static constexpr DisplayExclusiveFullscreenControl DEFAULT_DISPLAY_EXCLUSIVE_FULLSCREEN_CONTROL = static constexpr DisplayExclusiveFullscreenControl DEFAULT_DISPLAY_EXCLUSIVE_FULLSCREEN_CONTROL =
DisplayExclusiveFullscreenControl::Automatic; DisplayExclusiveFullscreenControl::Automatic;

View file

@ -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_24bit_chroma_smoothing != old_settings.display_24bit_chroma_smoothing ||
g_settings.display_crop_mode != old_settings.display_crop_mode || g_settings.display_crop_mode != old_settings.display_crop_mode ||
g_settings.display_aspect_ratio != old_settings.display_aspect_ratio || 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_scaling != old_settings.display_scaling ||
g_settings.display_show_gpu_usage != old_settings.display_show_gpu_usage || g_settings.display_show_gpu_usage != old_settings.display_show_gpu_usage ||
g_settings.gpu_pgxp_enable != old_settings.gpu_pgxp_enable || 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())) / (static_cast<float>(g_gpu->GetCRTCDisplayWidth()) / static_cast<float>(g_gpu->GetCRTCDisplayHeight())) /
g_gpu->ComputeDisplayAspectRatio(); 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); 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); 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)); Host::RequestResizeHostDisplay(static_cast<s32>(requested_width), static_cast<s32>(requested_height));
} }

View file

@ -152,6 +152,15 @@ enum class DisplayAlignment : u8
Count Count
}; };
enum class DisplayRotation : u8
{
Normal,
Rotate90,
Rotate180,
Rotate270,
Count
};
enum class DisplayScalingMode : u8 enum class DisplayScalingMode : u8
{ {
Nearest, Nearest,

View file

@ -116,6 +116,9 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayAlignment, "Display", "Alignment", SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayAlignment, "Display", "Alignment",
&Settings::ParseDisplayAlignment, &Settings::GetDisplayAlignmentName, &Settings::ParseDisplayAlignment, &Settings::GetDisplayAlignmentName,
Settings::DEFAULT_DISPLAY_ALIGNMENT); Settings::DEFAULT_DISPLAY_ALIGNMENT);
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayRotation, "Display", "Rotation",
&Settings::ParseDisplayRotation, &Settings::GetDisplayRotationName,
Settings::DEFAULT_DISPLAY_ROTATION);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.gpuThread, "GPU", "UseThread", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.gpuThread, "GPU", "UseThread", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.threadedPresentation, "GPU", "ThreadedPresentation", SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.threadedPresentation, "GPU", "ThreadedPresentation",
Settings::DEFAULT_THREADED_PRESENTATION); Settings::DEFAULT_THREADED_PRESENTATION);
@ -596,6 +599,12 @@ void GraphicsSettingsWidget::setupAdditionalUi()
QString::fromUtf8(Settings::GetDisplayAlignmentDisplayName(static_cast<DisplayAlignment>(i)))); QString::fromUtf8(Settings::GetDisplayAlignmentDisplayName(static_cast<DisplayAlignment>(i))));
} }
for (u32 i = 0; i < static_cast<u32>(DisplayRotation::Count); i++)
{
m_ui.displayRotation->addItem(
QString::fromUtf8(Settings::GetDisplayRotationDisplayName(static_cast<DisplayRotation>(i))));
}
for (u32 i = 0; i < static_cast<u32>(GPULineDetectMode::Count); i++) for (u32 i = 0; i < static_cast<u32>(GPULineDetectMode::Count); i++)
{ {
m_ui.gpuLineDetectMode->addItem( m_ui.gpuLineDetectMode->addItem(

View file

@ -320,7 +320,7 @@
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_8" stretch="1,0"> <layout class="QHBoxLayout" name="horizontalLayout_8">
<item> <item>
<widget class="QComboBox" name="fullscreenMode"/> <widget class="QComboBox" name="fullscreenMode"/>
</item> </item>
@ -336,9 +336,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1">
<widget class="QComboBox" name="displayAlignment"/>
</item>
<item row="2" column="0" colspan="2"> <item row="2" column="0" colspan="2">
<layout class="QGridLayout" name="advancedDisplayOptionsLayout"> <layout class="QGridLayout" name="advancedDisplayOptionsLayout">
<item row="0" column="0"> <item row="0" column="0">
@ -371,6 +368,16 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="displayAlignment"/>
</item>
<item>
<widget class="QComboBox" name="displayRotation"/>
</item>
</layout>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View file

@ -220,6 +220,7 @@ const char* QtHost::GetDefaultLanguage()
static constexpr const ImWchar s_base_latin_range[] = { static constexpr const ImWchar s_base_latin_range[] = {
0x0020, 0x00FF, // Basic Latin + Latin Supplement 0x0020, 0x00FF, // Basic Latin + Latin Supplement
0x00B0, 0x00B0, // Degree sign
0x2022, 0x2022, // General punctuation 0x2022, 0x2022, // General punctuation
}; };
static constexpr const ImWchar s_central_european_ranges[] = { static constexpr const ImWchar s_central_european_ranges[] = {