From e2b77d17d8228852ad005e808c0c48ac3c1d557b Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sun, 7 Feb 2021 21:07:59 +1000 Subject: [PATCH] HostDisplay: Support rotation --- src/common/gl/program.h | 3 +- src/core/host_display.cpp | 5 + src/core/host_display.h | 11 ++ src/frontend-common/opengl_host_display.cpp | 130 +++++++++++++++++++- src/frontend-common/opengl_host_display.h | 7 ++ 5 files changed, 150 insertions(+), 6 deletions(-) diff --git a/src/common/gl/program.h b/src/common/gl/program.h index d877a4622..3fa9a7a74 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 9d8758bae..4d9511b09 100644 --- a/src/core/host_display.cpp +++ b/src/core/host_display.cpp @@ -91,6 +91,11 @@ bool HostDisplay::GetHostRefreshRate(float* refresh_rate) return g_host_interface->GetMainDisplayRefreshRate(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 69a285317..3a30e10c1 100644 --- a/src/core/host_display.h +++ b/src/core/host_display.h @@ -52,6 +52,15 @@ public: RightOrBottom }; + enum class Rotation + { + None, + R90Degrees, + R180Degrees, + R270Degrees, + Count + }; + virtual ~HostDisplay(); ALWAYS_INLINE s32 GetWindowWidth() const { return static_cast(m_window_info.surface_width); } @@ -182,6 +191,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; } @@ -260,6 +270,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/frontend-common/opengl_host_display.cpp b/src/frontend-common/opengl_host_display.cpp index de0bd3eaa..dd2652936 100644 --- a/src/frontend-common/opengl_host_display.cpp +++ b/src/frontend-common/opengl_host_display.cpp @@ -185,6 +185,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 { return (std::get<0>(s_display_pixel_format_mapping[static_cast(format)]) != static_cast(0)); @@ -417,7 +488,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()) @@ -476,6 +547,7 @@ bool OpenGLHostDisplay::ChangeRenderWindow(const WindowInfo& new_wi) ImGui::GetIO().DisplaySize.y = static_cast(m_window_info.surface_height); } + UpdateDisplayRotationFramebuffer(); return true; } @@ -493,6 +565,8 @@ void OpenGLHostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_window_ 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 @@ -544,12 +618,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); } )"; @@ -601,12 +680,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); @@ -733,7 +814,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); @@ -744,6 +825,12 @@ bool OpenGLHostDisplay::Render() RenderSoftwareCursor(); + if (m_display_rotation_framebuffer_fbo != 0) + { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + RenderRotatedFramebuffer(); + } + m_gl_context->SwapBuffers(); return true; } @@ -775,6 +862,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) { @@ -822,6 +916,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); @@ -837,6 +933,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 10493cdde..5d03ba949 100644 --- a/src/frontend-common/opengl_host_display.h +++ b/src/frontend-common/opengl_host_display.h @@ -48,6 +48,8 @@ public: virtual 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; @@ -78,6 +80,8 @@ protected: void BindDisplayPixelsTexture(); void UpdateDisplayPixelsTextureFilter(); + void UpdateDisplayRotationFramebuffer(); + void RenderRotatedFramebuffer(); void RenderDisplay(); void RenderImGui(); @@ -115,6 +119,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;