diff --git a/src/common/gl/program.h b/src/common/gl/program.h index d877a462..3fa9a7a7 100644 --- a/src/common/gl/program.h +++ b/src/common/gl/program.h @@ -40,6 +40,7 @@ public: void Destroy(); int RegisterUniform(const char* name); + GLint GetUniformLocation(int index) const { return m_uniform_locations[index]; } void Uniform1ui(int index, u32 x) const; void Uniform2ui(int index, u32 x, u32 y) const; void Uniform3ui(int index, u32 x, u32 y, u32 z) const; @@ -97,6 +98,6 @@ private: GLuint m_fragment_shader_id = 0; std::vector m_uniform_locations; -}; +}; // namespace GL } // namespace GL \ No newline at end of file diff --git a/src/core/host_display.cpp b/src/core/host_display.cpp index d0469fd3..790be30f 100644 --- a/src/core/host_display.cpp +++ b/src/core/host_display.cpp @@ -103,6 +103,11 @@ bool HostDisplay::GetHostRefreshRate(float* refresh_rate) return WindowInfo::QueryRefreshRateForWindow(m_window_info, refresh_rate); } +bool HostDisplay::SetDisplayRotation(Rotation rotation) +{ + return false; +} + void HostDisplay::SetSoftwareCursor(std::unique_ptr texture, float scale /*= 1.0f*/) { m_cursor_texture = std::move(texture); diff --git a/src/core/host_display.h b/src/core/host_display.h index ea01f846..b5a3563e 100644 --- a/src/core/host_display.h +++ b/src/core/host_display.h @@ -60,6 +60,15 @@ public: std::vector fullscreen_modes; }; + enum class Rotation + { + None, + R90Degrees, + R180Degrees, + R270Degrees, + Count + }; + virtual ~HostDisplay(); ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_window_info; } @@ -201,6 +210,7 @@ public: virtual bool SetDisplayPixels(HostDisplayPixelFormat format, u32 width, u32 height, const void* buffer, u32 pitch); virtual bool GetHostRefreshRate(float* refresh_rate); + virtual bool SetDisplayRotation(Rotation rotation); void SetDisplayLinearFiltering(bool enabled) { m_display_linear_filtering = enabled; } void SetDisplayTopMargin(s32 height) { m_display_top_margin = height; } @@ -283,6 +293,7 @@ protected: s32 m_display_top_margin = 0; Alignment m_display_alignment = Alignment::Center; + Rotation m_display_rotation = Rotation::None; std::unique_ptr m_cursor_texture; float m_cursor_texture_scale = 1.0f; diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 833b6b9f..4381b848 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -230,6 +230,7 @@ void Settings::Load(SettingsInterface& si) display_line_start_offset = static_cast(si.GetIntValue("Display", "LineStartOffset", 0)); display_line_end_offset = static_cast(si.GetIntValue("Display", "LineEndOffset", 0)); display_linear_filtering = si.GetBoolValue("Display", "LinearFiltering", true); + display_rotate = static_cast(si.GetIntValue("Display", "Rotate", 0)); display_integer_scaling = si.GetBoolValue("Display", "IntegerScaling", false); display_stretch = si.GetBoolValue("Display", "Stretch", false); display_post_processing = si.GetBoolValue("Display", "PostProcessing", false); diff --git a/src/core/settings.h b/src/core/settings.h index e837f8b2..199ecac7 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -136,6 +136,7 @@ struct Settings s16 display_active_end_offset = 0; s8 display_line_start_offset = 0; s8 display_line_end_offset = 0; + s8 display_rotate = 0; bool display_force_4_3_for_24bit = false; bool gpu_24bit_chroma_smoothing = false; bool display_linear_filtering = true; diff --git a/src/duckstation-nogui/nogui_host_interface.cpp b/src/duckstation-nogui/nogui_host_interface.cpp index 638af4ea..6cef355a 100644 --- a/src/duckstation-nogui/nogui_host_interface.cpp +++ b/src/duckstation-nogui/nogui_host_interface.cpp @@ -136,6 +136,15 @@ bool NoGUIHostInterface::CreateDisplay(bool fullscreen) return false; } + switch(g_settings.display_rotate & 3) + { + case 1: m_display->SetDisplayRotation(HostDisplay::Rotation::R90Degrees); + case 2: m_display->SetDisplayRotation(HostDisplay::Rotation::R180Degrees); + case 3: m_display->SetDisplayRotation(HostDisplay::Rotation::R270Degrees); + case 0: + default: break; + } + if (fullscreen) SetFullscreen(true); diff --git a/src/frontend-common/opengl_host_display.cpp b/src/frontend-common/opengl_host_display.cpp index 7092b455..ff1c7939 100644 --- a/src/frontend-common/opengl_host_display.cpp +++ b/src/frontend-common/opengl_host_display.cpp @@ -203,6 +203,77 @@ void OpenGLHostDisplay::UpdateDisplayPixelsTextureFilter() m_display_texture_is_linear_filtered = m_display_linear_filtering; } +bool OpenGLHostDisplay::SetDisplayRotation(Rotation rotation) +{ + m_display_rotation = rotation; + UpdateDisplayRotationFramebuffer(); + return true; +} + +void OpenGLHostDisplay::UpdateDisplayRotationFramebuffer() +{ + m_window_info.surface_width = m_gl_context->GetSurfaceWidth(); + m_window_info.surface_height = m_gl_context->GetSurfaceHeight(); + + if (m_display_rotation_framebuffer_fbo != 0) + { + glDeleteFramebuffers(1, &m_display_rotation_framebuffer_fbo); + m_display_rotation_framebuffer_fbo = 0; + glDeleteTextures(1, &m_display_rotation_framebuffer_texture); + m_display_rotation_framebuffer_texture = 0; + } + + if (m_display_rotation != Rotation::None) + { + if (m_display_rotation_framebuffer_texture == 0) + glGenTextures(1, &m_display_rotation_framebuffer_texture); + if (m_display_rotation_framebuffer_fbo == 0) + glGenFramebuffers(1, &m_display_rotation_framebuffer_fbo); + + if (m_display_rotation == Rotation::R90Degrees || m_display_rotation == Rotation::R270Degrees) + std::swap(m_window_info.surface_width, m_window_info.surface_height); + + GLint old_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &old_texture); + + glBindTexture(GL_TEXTURE_2D, m_display_rotation_framebuffer_texture); + + if (GLAD_GL_ARB_texture_storage || GLAD_GL_ES_VERSION_3_1) + { + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, m_window_info.surface_width, m_window_info.surface_height); + } + else + { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_window_info.surface_width, m_window_info.surface_height, 0, GL_RGBA, + GL_UNSIGNED_BYTE, nullptr); + } + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); + + const GLenum framebuffer_binding = m_use_gles2_draw_path ? GL_FRAMEBUFFER : GL_DRAW_FRAMEBUFFER; + GLint old_framebuffer; + glGetIntegerv(m_use_gles2_draw_path ? GL_FRAMEBUFFER_BINDING : GL_DRAW_FRAMEBUFFER_BINDING, &old_framebuffer); + + glBindFramebuffer(framebuffer_binding, m_display_rotation_framebuffer_fbo); + glFramebufferTexture2D(framebuffer_binding, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + m_display_rotation_framebuffer_texture, 0); + Assert(glCheckFramebufferStatus(framebuffer_binding) == GL_FRAMEBUFFER_COMPLETE); + + glBindFramebuffer(framebuffer_binding, old_framebuffer); + glBindTexture(GL_TEXTURE_2D, old_texture); + } + + if (ImGui::GetCurrentContext()) + { + ImGui::GetIO().DisplaySize = + ImVec2(static_cast(m_window_info.surface_width), static_cast(m_window_info.surface_height)); + } +} + bool OpenGLHostDisplay::SupportsDisplayPixelFormat(HostDisplayPixelFormat format) const { const auto [gl_internal_format, gl_format, gl_type] = GetPixelFormatMapping(m_gl_context->IsGLES(), format); @@ -435,7 +506,7 @@ bool OpenGLHostDisplay::InitializeRenderDevice(std::string_view shader_cache_dir glDebugMessageCallback(GLDebugCallback, nullptr); glEnable(GL_DEBUG_OUTPUT); - // glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); } if (!CreateResources()) @@ -484,7 +555,17 @@ bool OpenGLHostDisplay::ChangeRenderWindow(const WindowInfo& new_wi) return false; } - m_window_info = m_gl_context->GetWindowInfo(); + m_window_info = new_wi; + m_window_info.surface_width = m_gl_context->GetSurfaceWidth(); + m_window_info.surface_height = m_gl_context->GetSurfaceHeight(); + + if (ImGui::GetCurrentContext()) + { + ImGui::GetIO().DisplaySize.x = static_cast(m_window_info.surface_width); + ImGui::GetIO().DisplaySize.y = static_cast(m_window_info.surface_height); + } + + UpdateDisplayRotationFramebuffer(); return true; } @@ -494,7 +575,16 @@ void OpenGLHostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_window_ return; m_gl_context->ResizeSurface(static_cast(new_window_width), static_cast(new_window_height)); - m_window_info = m_gl_context->GetWindowInfo(); + m_window_info.surface_width = m_gl_context->GetSurfaceWidth(); + m_window_info.surface_height = m_gl_context->GetSurfaceHeight(); + + if (ImGui::GetCurrentContext()) + { + ImGui::GetIO().DisplaySize.x = static_cast(m_window_info.surface_width); + ImGui::GetIO().DisplaySize.y = static_cast(m_window_info.surface_height); + } + + UpdateDisplayRotationFramebuffer(); } bool OpenGLHostDisplay::SupportsFullscreen() const @@ -560,12 +650,17 @@ bool OpenGLHostDisplay::CreateResources() { static constexpr char fullscreen_quad_vertex_shader[] = R"( uniform vec4 u_src_rect; +uniform mat2 u_rotation_matrix; out vec2 v_tex0; void main() { vec2 pos = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2)); - v_tex0 = u_src_rect.xy + pos * u_src_rect.zw; + v_tex0 = (u_src_rect.xy + pos * u_src_rect.zw); + + vec2 center = vec2(0.5, 0.5); + v_tex0 = center + (u_rotation_matrix * (v_tex0 - center)); + gl_Position = vec4(pos * vec2(2.0f, -2.0f) + vec2(-1.0f, 1.0f), 0.0f, 1.0f); } )"; @@ -617,12 +712,14 @@ void main() m_display_program.Bind(); m_display_program.RegisterUniform("u_src_rect"); + m_display_program.RegisterUniform("u_rotation_matrix"); m_display_program.RegisterUniform("samp0"); - m_display_program.Uniform1i(1, 0); + m_display_program.Uniform1i(2, 0); m_cursor_program.Bind(); m_cursor_program.RegisterUniform("u_src_rect"); + m_cursor_program.RegisterUniform("u_rotation_matrix"); m_cursor_program.RegisterUniform("samp0"); - m_cursor_program.Uniform1i(1, 0); + m_cursor_program.Uniform1i(2, 0); glGenVertexArrays(1, &m_display_vao); @@ -749,7 +846,7 @@ bool OpenGLHostDisplay::Render() } glDisable(GL_SCISSOR_TEST); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_display_rotation_framebuffer_fbo); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); @@ -760,6 +857,12 @@ bool OpenGLHostDisplay::Render() RenderSoftwareCursor(); + if (m_display_rotation_framebuffer_fbo != 0) + { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + RenderRotatedFramebuffer(); + } + m_gl_context->SwapBuffers(); return true; } @@ -833,6 +936,13 @@ void OpenGLHostDisplay::RenderDisplay() m_display_texture_view_width, m_display_texture_view_height, m_display_linear_filtering); } +static const std::array, static_cast(HostDisplay::Rotation::Count)> s_rotation_matrices = {{ + {{1.0f, 0.0f, 0.0f, 1.0f}}, + {{0.0f, 1.0f, 1.0f, 0.0f}}, + {{-1.0f, 0.0f, 0.0f, 1.0f}}, + {{0.0f, -1.0f, -1.0f, 0.0f}}, +}}; + static void DrawFullscreenQuadES2(s32 tex_view_x, s32 tex_view_y, s32 tex_view_width, s32 tex_view_height, s32 tex_width, s32 tex_height) { @@ -879,6 +989,8 @@ void OpenGLHostDisplay::RenderDisplay(s32 left, s32 bottom, s32 width, s32 heigh (static_cast(texture_view_y) + (position_adjust * flip_adjust)) / static_cast(texture_height), (static_cast(texture_view_width) - size_adjust) / static_cast(texture_width), (static_cast(texture_view_height) - (size_adjust * flip_adjust)) / static_cast(texture_height)); + glUniformMatrix2fv(m_display_program.GetUniformLocation(1), 1, GL_TRUE, + s_rotation_matrices[static_cast(HostDisplay::Rotation::None)].data()); glBindSampler(0, linear_filter ? m_display_linear_sampler : m_display_nearest_sampler); glBindVertexArray(m_display_vao); glDrawArrays(GL_TRIANGLES, 0, 3); @@ -894,6 +1006,30 @@ void OpenGLHostDisplay::RenderDisplay(s32 left, s32 bottom, s32 width, s32 heigh } } +void OpenGLHostDisplay::RenderRotatedFramebuffer() +{ + const u32 width = m_gl_context->GetSurfaceWidth(); + const u32 height = m_gl_context->GetSurfaceHeight(); + + glViewport(0, 0, width, height); + glDisable(GL_BLEND); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_SCISSOR_TEST); + glDepthMask(GL_FALSE); + + glClear(GL_COLOR_BUFFER_BIT); + + m_display_program.Bind(); + m_display_program.Uniform4f(0, 0.0f, 0.0f, 1.0f, 1.0f); + glUniformMatrix2fv(m_display_program.GetUniformLocation(1), 1, GL_TRUE, + s_rotation_matrices[static_cast(m_display_rotation)].data()); + glBindTexture(GL_TEXTURE_2D, m_display_rotation_framebuffer_texture); + + glBindVertexArray(m_display_vao); + glDrawArrays(GL_TRIANGLES, 0, 3); +} + void OpenGLHostDisplay::RenderSoftwareCursor() { if (!HasSoftwareCursor()) diff --git a/src/frontend-common/opengl_host_display.h b/src/frontend-common/opengl_host_display.h index 23e8d700..585ab4b5 100644 --- a/src/frontend-common/opengl_host_display.h +++ b/src/frontend-common/opengl_host_display.h @@ -51,6 +51,8 @@ public: bool SetPostProcessingChain(const std::string_view& config) override; + bool SetDisplayRotation(Rotation rotation) override; + std::unique_ptr CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, HostDisplayPixelFormat format, const void* data, u32 data_stride, bool dynamic = false) override; @@ -83,6 +85,8 @@ protected: void BindDisplayPixelsTexture(); void UpdateDisplayPixelsTextureFilter(); + void UpdateDisplayRotationFramebuffer(); + void RenderRotatedFramebuffer(); void RenderDisplay(); void RenderImGui(); @@ -121,6 +125,9 @@ protected: u32 m_display_pixels_texture_pbo_map_size = 0; std::vector m_gles_pixels_repack_buffer; + GLuint m_display_rotation_framebuffer_texture = 0; + GLuint m_display_rotation_framebuffer_fbo = 0; + PostProcessingChain m_post_processing_chain; GL::Texture m_post_processing_input_texture; std::unique_ptr m_post_processing_ubo;