diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index edc2d1759..606167bc2 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -1997,15 +1997,11 @@ GPUDevice::PresentResult GPU::RenderDisplay(GPUTexture* target, const GSVector4i // Now we can apply the post chain. GPUTexture* post_output_texture = PostProcessing::InternalChain.GetOutputTexture(); - if (const GPUDevice::PresentResult pres = PostProcessing::InternalChain.Apply( - display_texture, m_display_depth_buffer, post_output_texture, - GSVector4i(0, 0, display_texture_view_width, display_texture_view_height), display_texture_view_width, - display_texture_view_height, m_crtc_state.display_width, m_crtc_state.display_height); - pres != GPUDevice::PresentResult::OK) - { - return pres; - } - else + if (PostProcessing::InternalChain.Apply(display_texture, m_display_depth_buffer, post_output_texture, + GSVector4i(0, 0, display_texture_view_width, display_texture_view_height), + display_texture_view_width, display_texture_view_height, + m_crtc_state.display_width, + m_crtc_state.display_height) == GPUDevice::PresentResult::OK) { display_texture_view_x = 0; display_texture_view_y = 0; @@ -2020,8 +2016,13 @@ GPUDevice::PresentResult GPU::RenderDisplay(GPUTexture* target, const GSVector4i const bool really_postfx = (postfx && PostProcessing::DisplayChain.IsActive() && g_gpu_device->HasMainSwapChain() && hdformat != GPUTexture::Format::Unknown && target_width > 0 && target_height > 0 && PostProcessing::DisplayChain.CheckTargets(hdformat, target_width, target_height)); - const GSVector4i real_draw_rect = - g_gpu_device->UsesLowerLeftOrigin() ? GPUDevice::FlipToLowerLeft(draw_rect, target_height) : draw_rect; + GSVector4i real_draw_rect = target ? draw_rect : g_gpu_device->GetMainSwapChain()->PreRotateClipRect(draw_rect); + if (g_gpu_device->UsesLowerLeftOrigin()) + { + real_draw_rect = GPUDevice::FlipToLowerLeft( + real_draw_rect, + (target || really_postfx) ? target_height : g_gpu_device->GetMainSwapChain()->GetPostRotatedHeight()); + } if (really_postfx) { g_gpu_device->ClearRenderTarget(PostProcessing::DisplayChain.GetInputTexture(), GPUDevice::DEFAULT_CLEAR_COLOR); @@ -2106,16 +2107,22 @@ GPUDevice::PresentResult GPU::RenderDisplay(GPUTexture* target, const GSVector4i uniforms.src_size[2] = rcp_width; uniforms.src_size[3] = rcp_height; - if (g_settings.display_rotation != DisplayRotation::Normal) + const WindowInfo::PreRotation surface_prerotation = (target || really_postfx) ? + WindowInfo::PreRotation::Identity : + g_gpu_device->GetMainSwapChain()->GetPreRotation(); + if (g_settings.display_rotation != DisplayRotation::Normal || + surface_prerotation != WindowInfo::PreRotation::Identity) { - static constexpr const std::array(DisplayRotation::Count) - 1> rotation_radians = {{ + static constexpr const std::array(DisplayRotation::Count)> rotation_radians = {{ + 0.0f, // Disabled static_cast(std::numbers::pi * 1.5f), // Rotate90 static_cast(std::numbers::pi), // Rotate180 static_cast(std::numbers::pi / 2.0), // Rotate270 }}; - GSMatrix2x2::Rotation(rotation_radians[static_cast(g_settings.display_rotation) - 1]) - .store(uniforms.rotation_matrix); + const u32 rotation_idx = (static_cast(g_settings.display_rotation) + static_cast(surface_prerotation)) % + static_cast(rotation_radians.size()); + GSMatrix2x2::Rotation(rotation_radians[rotation_idx]).store(uniforms.rotation_matrix); } else { diff --git a/src/util/gpu_device.cpp b/src/util/gpu_device.cpp index 1ce76f115..231283c2a 100644 --- a/src/util/gpu_device.cpp +++ b/src/util/gpu_device.cpp @@ -233,6 +233,47 @@ GPUSwapChain::GPUSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool a GPUSwapChain::~GPUSwapChain() = default; +GSVector4i GPUSwapChain::PreRotateClipRect(const GSVector4i& v) +{ + GSVector4i new_clip; + switch (m_window_info.surface_prerotation) + { + case WindowInfo::PreRotation::Identity: + new_clip = v; + break; + + case WindowInfo::PreRotation::Rotate90Clockwise: + { + const s32 height = (v.w - v.y); + const s32 y = m_window_info.surface_height - v.y - height; + new_clip = GSVector4i(y, v.x, y + height, v.z); + } + break; + + case WindowInfo::PreRotation::Rotate180Clockwise: + { + const s32 width = (v.z - v.x); + const s32 height = (v.w - v.y); + const s32 x = m_window_info.surface_width - v.x - width; + const s32 y = m_window_info.surface_height - v.y - height; + new_clip = GSVector4i(x, y, x + width, y + height); + } + break; + + case WindowInfo::PreRotation::Rotate270Clockwise: + { + const s32 width = (v.z - v.x); + const s32 x = m_window_info.surface_width - v.x - width; + new_clip = GSVector4i(v.y, x, v.w, x + width); + } + break; + + DefaultCaseIsUnreachable() + } + + return new_clip; +} + bool GPUSwapChain::ShouldSkipPresentingFrame() { // Only needed with FIFO. But since we're so fast, we allow it always. @@ -674,11 +715,17 @@ void GPUDevice::RenderImGui(GPUSwapChain* swap_chain) if (draw_data->CmdListsCount == 0 || !swap_chain) return; + const s32 post_rotated_height = swap_chain->GetPostRotatedHeight(); SetPipeline(m_imgui_pipeline.get()); - SetViewportAndScissor(0, 0, swap_chain->GetWidth(), swap_chain->GetHeight()); + SetViewport(0, 0, swap_chain->GetPostRotatedWidth(), post_rotated_height); - const GSMatrix4x4 mproj = GSMatrix4x4::OffCenterOrthographicProjection( + GSMatrix4x4 mproj = GSMatrix4x4::OffCenterOrthographicProjection( 0.0f, 0.0f, static_cast(swap_chain->GetWidth()), static_cast(swap_chain->GetHeight()), 0.0f, 1.0f); + if (swap_chain->GetPreRotation() != WindowInfo::PreRotation::Identity) + { + mproj = + GSMatrix4x4::RotationZ(WindowInfo::GetZRotationForPreRotation(swap_chain->GetPreRotation())) * mproj; + } PushUniformBuffer(&mproj, sizeof(mproj)); // Render command lists @@ -701,8 +748,9 @@ void GPUDevice::RenderImGui(GPUSwapChain* swap_chain) continue; GSVector4i clip = GSVector4i(GSVector4::load(&pcmd->ClipRect.x)); + clip = swap_chain->PreRotateClipRect(clip); if (flip) - clip = FlipToLowerLeft(clip, swap_chain->GetHeight()); + clip = FlipToLowerLeft(clip, post_rotated_height); SetScissor(clip); SetTextureSampler(0, reinterpret_cast(pcmd->TextureId), m_linear_sampler.get()); diff --git a/src/util/gpu_device.h b/src/util/gpu_device.h index c2b37f2fb..ffcd7fd10 100644 --- a/src/util/gpu_device.h +++ b/src/util/gpu_device.h @@ -507,7 +507,10 @@ public: ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_window_info; } ALWAYS_INLINE u32 GetWidth() const { return m_window_info.surface_width; } ALWAYS_INLINE u32 GetHeight() const { return m_window_info.surface_height; } + ALWAYS_INLINE u32 GetPostRotatedWidth() const { return m_window_info.GetPostRotatedWidth(); } + ALWAYS_INLINE u32 GetPostRotatedHeight() const { return m_window_info.GetPostRotatedHeight(); } ALWAYS_INLINE float GetScale() const { return m_window_info.surface_scale; } + ALWAYS_INLINE WindowInfo::PreRotation GetPreRotation() const { return m_window_info.surface_prerotation; } ALWAYS_INLINE GPUTexture::Format GetFormat() const { return m_window_info.surface_format; } ALWAYS_INLINE GPUVSyncMode GetVSyncMode() const { return m_vsync_mode; } @@ -517,6 +520,8 @@ public: virtual bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) = 0; virtual bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) = 0; + GSVector4i PreRotateClipRect(const GSVector4i& v); + bool ShouldSkipPresentingFrame(); void ThrottlePresentation(); diff --git a/src/util/opengl_context_egl.cpp b/src/util/opengl_context_egl.cpp index 43f6d8ab8..a2ddc9061 100644 --- a/src/util/opengl_context_egl.cpp +++ b/src/util/opengl_context_egl.cpp @@ -443,6 +443,9 @@ void OpenGLContextEGL::UpdateWindowInfoSize(WindowInfo& wi, EGLSurface surface) { wi.surface_width = static_cast(surface_width); wi.surface_height = static_cast(surface_height); + + if (WindowInfo::ShouldSwapDimensionsForPreRotation(wi.surface_prerotation)) + std::swap(wi.surface_width, wi.surface_height); } else { diff --git a/src/util/postprocessing.cpp b/src/util/postprocessing.cpp index 79717708e..81405ca86 100644 --- a/src/util/postprocessing.cpp +++ b/src/util/postprocessing.cpp @@ -8,6 +8,7 @@ #include "postprocessing_shader.h" #include "postprocessing_shader_fx.h" #include "postprocessing_shader_glsl.h" +#include "shadergen.h" // TODO: Remove me #include "core/host.h" @@ -28,9 +29,6 @@ LOG_CHANNEL(PostProcessing); -// TODO: ProgressCallbacks for shader compiling, it can be a bit slow. -// TODO: buffer width/height is wrong on resize, need to change it somehow. - namespace PostProcessing { template static u32 ParseVector(std::string_view line, ShaderOption::ValueVector* values); @@ -369,6 +367,11 @@ PostProcessing::Chain::Chain(const char* section) : m_section(section) PostProcessing::Chain::~Chain() = default; +GPUTexture* PostProcessing::Chain::GetTextureUnusedAtEndOfChain() const +{ + return (m_stages.size() % 2) ? m_output_texture.get() : m_input_texture.get(); +} + bool PostProcessing::Chain::IsActive() const { return m_enabled && !m_stages.empty(); @@ -561,16 +564,58 @@ bool PostProcessing::Chain::CheckTargets(GPUTexture::Format target_format, u32 t if (m_target_format == target_format && m_target_width == target_width && m_target_height == target_height) return true; + Error error; + + if (!IsInternalChain() && (!m_rotated_copy_pipeline || m_target_format != target_format)) + { + const RenderAPI rapi = g_gpu_device->GetRenderAPI(); + const ShaderGen shadergen(rapi, ShaderGen::GetShaderLanguageForAPI(rapi), false, false); + const std::unique_ptr vso = g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(), + shadergen.GenerateRotateVertexShader(), &error); + const std::unique_ptr fso = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), + shadergen.GenerateRotateFragmentShader(), &error); + if (!vso || !fso) + { + ERROR_LOG("Failed to compile post-processing rotate shaders: {}", error.GetDescription()); + return false; + } + GL_OBJECT_NAME(vso, "Post-processing rotate blit VS"); + GL_OBJECT_NAME(vso, "Post-processing rotate blit FS"); + + const GPUPipeline::GraphicsConfig config = {.layout = GPUPipeline::Layout::SingleTextureAndPushConstants, + .primitive = GPUPipeline::Primitive::Triangles, + .input_layout = {}, + .rasterization = GPUPipeline::RasterizationState::GetNoCullState(), + .depth = GPUPipeline::DepthState::GetNoTestsState(), + .blend = GPUPipeline::BlendState::GetNoBlendingState(), + .vertex_shader = vso.get(), + .geometry_shader = nullptr, + .fragment_shader = fso.get(), + .color_formats = {target_format}, + .depth_format = GPUTexture::Format::Unknown, + .samples = 1, + .per_sample_shading = false, + .render_pass_flags = GPUPipeline::NoRenderPassFlags}; + m_rotated_copy_pipeline = g_gpu_device->CreatePipeline(config, &error); + if (!m_rotated_copy_pipeline) + { + ERROR_LOG("Failed to compile post-processing rotate pipeline: {}", error.GetDescription()); + return false; + } + GL_OBJECT_NAME(m_rotated_copy_pipeline, "Post-processing rotate pipeline"); + } + // In case any allocs fail. DestroyTextures(); if (!(m_input_texture = g_gpu_device->FetchTexture(target_width, target_height, 1, 1, 1, GPUTexture::Type::RenderTarget, - target_format, GPUTexture::Flags::None)) || + target_format, GPUTexture::Flags::None, nullptr, 0, &error)) || !(m_output_texture = g_gpu_device->FetchTexture(target_width, target_height, 1, 1, 1, GPUTexture::Type::RenderTarget, - target_format, GPUTexture::Flags::None))) + target_format, GPUTexture::Flags::None, nullptr, 0, &error))) { + ERROR_LOG("Failed to create input/output textures: {}", error.GetDescription()); DestroyTextures(); return false; } @@ -583,7 +628,6 @@ bool PostProcessing::Chain::CheckTargets(GPUTexture::Format target_format, u32 t m_wants_depth_buffer = false; - Error error; for (size_t i = 0; i < m_stages.size(); i++) { Shader* const shader = m_stages[i].get(); @@ -622,6 +666,11 @@ void PostProcessing::Chain::DestroyTextures() g_gpu_device->RecycleTexture(std::move(m_input_texture)); } +void PostProcessing::Chain::DestroyPipelines() +{ + m_rotated_copy_pipeline.reset(); +} + GPUDevice::PresentResult PostProcessing::Chain::Apply(GPUTexture* input_color, GPUTexture* input_depth, GPUTexture* final_target, GSVector4i final_rect, s32 orig_width, s32 orig_height, s32 native_width, s32 native_height) @@ -633,13 +682,24 @@ GPUDevice::PresentResult PostProcessing::Chain::Apply(GPUTexture* input_color, G if (input_depth) input_depth->MakeReadyForSampling(); + GPUTexture* draw_final_target = final_target; + const WindowInfo::PreRotation prerotation = + final_target ? WindowInfo::PreRotation::Identity : g_gpu_device->GetMainSwapChain()->GetPreRotation(); + if (prerotation != WindowInfo::PreRotation::Identity) + { + // We have prerotation and post processing. This is messy, since we need to run the shader on the "real" size, + // then copy it across to the rotated image. We can use the input or output texture from the chain, whichever + // was not the last that was drawn to. + draw_final_target = GetTextureUnusedAtEndOfChain(); + } + for (const std::unique_ptr& stage : m_stages) { const bool is_final = (stage.get() == m_stages.back().get()); if (const GPUDevice::PresentResult pres = - stage->Apply(input_color, input_depth, is_final ? final_target : output, final_rect, orig_width, orig_height, - native_width, native_height, m_target_width, m_target_height); + stage->Apply(input_color, input_depth, is_final ? draw_final_target : output, final_rect, orig_width, + orig_height, native_width, native_height, m_target_width, m_target_height); pres != GPUDevice::PresentResult::OK) { return pres; @@ -653,6 +713,30 @@ GPUDevice::PresentResult PostProcessing::Chain::Apply(GPUTexture* input_color, G } } + if (prerotation != WindowInfo::PreRotation::Identity) + { + draw_final_target->MakeReadyForSampling(); + + // Rotate and blit to final swap chain. + GPUSwapChain* const swap_chain = g_gpu_device->GetMainSwapChain(); + if (const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(swap_chain); + pres != GPUDevice::PresentResult::OK) + { + return pres; + } + + GL_PUSH_FMT("Apply swap chain pre-rotation"); + + const GSMatrix2x2 rotmat = GSMatrix2x2::Rotation(WindowInfo::GetZRotationForPreRotation(prerotation)); + g_gpu_device->SetPipeline(m_rotated_copy_pipeline.get()); + g_gpu_device->PushUniformBuffer(&rotmat, sizeof(rotmat)); + g_gpu_device->SetTextureSampler(0, draw_final_target, g_gpu_device->GetNearestSampler()); + g_gpu_device->SetViewportAndScissor(0, 0, swap_chain->GetPostRotatedWidth(), swap_chain->GetPostRotatedHeight()); + g_gpu_device->Draw(3, 0); + + GL_POP(); + } + return GPUDevice::PresentResult::OK; } @@ -675,6 +759,7 @@ void PostProcessing::Shutdown() s_samplers.clear(); ForAllChains([](Chain& chain) { chain.ClearStages(); + chain.DestroyPipelines(); chain.DestroyTextures(); }); } @@ -691,6 +776,7 @@ bool PostProcessing::ReloadShaders() ForAllChains([](Chain& chain) { chain.ClearStages(); + chain.DestroyPipelines(); chain.DestroyTextures(); chain.LoadStages(); }); diff --git a/src/util/postprocessing.h b/src/util/postprocessing.h index 11c9d9444..6c78cb12f 100644 --- a/src/util/postprocessing.h +++ b/src/util/postprocessing.h @@ -13,6 +13,7 @@ class Timer; +class GPUPipeline; class GPUSampler; class GPUTexture; @@ -117,6 +118,9 @@ public: ALWAYS_INLINE GPUTexture* GetInputTexture() const { return m_input_texture.get(); } ALWAYS_INLINE GPUTexture* GetOutputTexture() const { return m_output_texture.get(); } + /// Returns either the input or output texture, whichever isn't the destination after the final pass. + GPUTexture* GetTextureUnusedAtEndOfChain() const; + bool IsActive() const; bool IsInternalChain() const; @@ -125,6 +129,7 @@ public: void LoadStages(); void ClearStages(); void DestroyTextures(); + void DestroyPipelines(); /// Temporarily toggles post-processing on/off. void Toggle(); @@ -151,6 +156,7 @@ private: std::vector> m_stages; std::unique_ptr m_input_texture; std::unique_ptr m_output_texture; + std::unique_ptr m_rotated_copy_pipeline; }; // [display_name, filename] diff --git a/src/util/shadergen.cpp b/src/util/shadergen.cpp index 2788206d9..98907cbb1 100644 --- a/src/util/shadergen.cpp +++ b/src/util/shadergen.cpp @@ -791,6 +791,40 @@ void ShaderGen::DeclareFragmentEntryPoint( } } +std::string ShaderGen::GenerateRotateVertexShader() const +{ + std::stringstream ss; + WriteHeader(ss); + DeclareUniformBuffer(ss, { "float2 u_rotation_matrix0", "float2 u_rotation_matrix1" }, true); + DeclareVertexEntryPoint(ss, {}, 0, 1, {}, true); + ss << "{\n"; + ss << " v_tex0 = float2(float((v_id << 1) & 2u), float(v_id & 2u));\n"; + ss << " v_pos = float4(v_tex0 * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);\n"; + ss << " v_pos.xy = float2(dot(u_rotation_matrix0, v_pos.xy), dot(u_rotation_matrix1, v_pos.xy));\n"; + ss << " #if API_OPENGL || API_OPENGL_ES || API_VULKAN\n"; + ss << " v_pos.y = -v_pos.y;\n"; + ss << " #endif\n"; + ss << "}\n"; + + return ss.str(); +} + +std::string ShaderGen::GenerateRotateFragmentShader() const +{ + std::stringstream ss; + WriteHeader(ss); + DeclareTexture(ss, "samp0", 0); + DeclareFragmentEntryPoint(ss, 0, 1); + + ss << R"( +{ + o_col0 = SAMPLE_TEXTURE(samp0, v_tex0); +} +)"; + + return ss.str(); +} + std::string ShaderGen::GenerateScreenQuadVertexShader(float z /* = 0.0f */) const { std::stringstream ss; diff --git a/src/util/shadergen.h b/src/util/shadergen.h index 714acbb2b..8685ec728 100644 --- a/src/util/shadergen.h +++ b/src/util/shadergen.h @@ -25,6 +25,9 @@ public: ALWAYS_INLINE GPUShaderLanguage GetLanguage() const { return m_shader_language; } + std::string GenerateRotateVertexShader() const; + std::string GenerateRotateFragmentShader() const; + std::string GenerateScreenQuadVertexShader(float z = 0.0f) const; std::string GenerateUVQuadVertexShader() const; std::string GenerateFillFragmentShader() const; diff --git a/src/util/vulkan_device.cpp b/src/util/vulkan_device.cpp index f61fb69a7..d88b0f2b9 100644 --- a/src/util/vulkan_device.cpp +++ b/src/util/vulkan_device.cpp @@ -3406,7 +3406,7 @@ void VulkanDevice::BeginSwapChainRenderPass(VulkanSwapChain* swap_chain, u32 cle const VkRenderingInfoKHR ri = {VK_STRUCTURE_TYPE_RENDERING_INFO_KHR, nullptr, 0u, - {{}, {swap_chain->GetWidth(), swap_chain->GetHeight()}}, + {{}, {swap_chain->GetPostRotatedWidth(), swap_chain->GetPostRotatedHeight()}}, 1u, 0u, 1u, @@ -3427,7 +3427,7 @@ void VulkanDevice::BeginSwapChainRenderPass(VulkanSwapChain* swap_chain, u32 cle nullptr, m_current_render_pass, swap_chain->GetCurrentFramebuffer(), - {{0, 0}, {swap_chain->GetWidth(), swap_chain->GetHeight()}}, + {{0, 0}, {swap_chain->GetPostRotatedWidth(), swap_chain->GetPostRotatedHeight()}}, 1u, &clear_value}; vkCmdBeginRenderPass(GetCurrentCommandBuffer(), &rp, VK_SUBPASS_CONTENTS_INLINE); diff --git a/src/util/vulkan_swap_chain.cpp b/src/util/vulkan_swap_chain.cpp index 34bceaf26..334e74b62 100644 --- a/src/util/vulkan_swap_chain.cpp +++ b/src/util/vulkan_swap_chain.cpp @@ -387,9 +387,50 @@ bool VulkanSwapChain::CreateSwapChain(VulkanDevice& dev, Error* error) surface_caps.surfaceCapabilities.maxImageExtent.height); // Prefer identity transform if possible + VkExtent2D window_size = size; + WindowInfo::PreRotation window_prerotation = WindowInfo::PreRotation::Identity; VkSurfaceTransformFlagBitsKHR transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; - if (!(surface_caps.surfaceCapabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)) - transform = surface_caps.surfaceCapabilities.currentTransform; + switch (surface_caps.surfaceCapabilities.currentTransform) + { + case VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR: + break; + + case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR: + transform = VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR; + window_prerotation = WindowInfo::PreRotation::Rotate90Clockwise; + std::swap(size.width, size.height); + DEV_LOG("Using VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR pretransform."); + break; + + case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR: + transform = VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR; + window_prerotation = WindowInfo::PreRotation::Rotate180Clockwise; + DEV_LOG("Using VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR pretransform."); + break; + + case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR: + transform = VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR; + window_prerotation = WindowInfo::PreRotation::Rotate270Clockwise; + std::swap(size.width, size.height); + DEV_LOG("Using VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR pretransform."); + break; + + default: + { + if (!(surface_caps.surfaceCapabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)) + { + WARNING_LOG("Unhandled surface transform 0x{:X}, identity unsupported.", + static_cast(surface_caps.surfaceCapabilities.supportedTransforms)); + transform = surface_caps.surfaceCapabilities.currentTransform; + } + else + { + WARNING_LOG("Unhandled surface transform 0x{:X}", + static_cast(surface_caps.surfaceCapabilities.supportedTransforms)); + } + } + break; + } VkCompositeAlphaFlagBitsKHR alpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; if (!(surface_caps.surfaceCapabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR)) @@ -486,17 +527,18 @@ bool VulkanSwapChain::CreateSwapChain(VulkanDevice& dev, Error* error) if (old_swap_chain != VK_NULL_HANDLE) vkDestroySwapchainKHR(vkdev, old_swap_chain, nullptr); - if (size.width == 0 || size.width > std::numeric_limits::max() || size.height == 0 || - size.height > std::numeric_limits::max()) + if (window_size.width == 0 || window_size.width > std::numeric_limits::max() || window_size.height == 0 || + window_size.height > std::numeric_limits::max()) { - Error::SetStringFmt(error, "Invalid swap chain dimensions: {}x{}", size.width, size.height); + Error::SetStringFmt(error, "Invalid swap chain dimensions: {}x{}", window_size.width, window_size.height); return false; } m_present_mode = present_mode.value(); - m_window_info.surface_width = static_cast(size.width); - m_window_info.surface_height = static_cast(size.height); + m_window_info.surface_width = static_cast(window_size.width); + m_window_info.surface_height = static_cast(window_size.height); m_window_info.surface_format = VulkanDevice::GetFormatForVkFormat(surface_format->format); + m_window_info.surface_prerotation = window_prerotation; if (m_window_info.surface_format == GPUTexture::Format::Unknown) { Error::SetStringFmt(error, "Unknown surface format {}", static_cast(surface_format->format)); @@ -534,6 +576,8 @@ bool VulkanSwapChain::CreateSwapChainImages(VulkanDevice& dev, Error* error) return false; } + const u32 fb_width = GetPostRotatedWidth(); + const u32 fb_height = GetPostRotatedHeight(); m_images.reserve(image_count); m_current_image = 0; for (u32 i = 0; i < image_count; i++) @@ -565,7 +609,7 @@ bool VulkanSwapChain::CreateSwapChainImages(VulkanDevice& dev, Error* error) Vulkan::FramebufferBuilder fbb; fbb.AddAttachment(image.view); fbb.SetRenderPass(render_pass); - fbb.SetSize(m_window_info.surface_width, m_window_info.surface_height, 1); + fbb.SetSize(fb_width, fb_height, 1); if ((image.framebuffer = fbb.Create(vkdev)) == VK_NULL_HANDLE) { Error::SetStringView(error, "Failed to create swap chain image framebuffer."); diff --git a/src/util/window_info.cpp b/src/util/window_info.cpp index 543bfedf6..0fc19ef24 100644 --- a/src/util/window_info.cpp +++ b/src/util/window_info.cpp @@ -9,8 +9,31 @@ #include "common/log.h" #include "common/scoped_guard.h" +#include +#include + LOG_CHANNEL(WindowInfo); +void WindowInfo::SetPreRotated(PreRotation prerotation) +{ + if (ShouldSwapDimensionsForPreRotation(prerotation) != ShouldSwapDimensionsForPreRotation(surface_prerotation)) + std::swap(surface_width, surface_height); + + surface_prerotation = prerotation; +} + +float WindowInfo::GetZRotationForPreRotation(PreRotation prerotation) +{ + static constexpr const std::array rotation_radians = {{ + 0.0f, // Identity + static_cast(std::numbers::pi * 1.5f), // Rotate90Clockwise + static_cast(std::numbers::pi), // Rotate180Clockwise + static_cast(std::numbers::pi / 2.0), // Rotate270Clockwise + }}; + + return rotation_radians[static_cast(prerotation)]; +} + #if defined(_WIN32) #include "common/windows_headers.h" diff --git a/src/util/window_info.h b/src/util/window_info.h index 4bb672b46..2584a30fd 100644 --- a/src/util/window_info.h +++ b/src/util/window_info.h @@ -24,8 +24,17 @@ struct WindowInfo Android, }; + enum class PreRotation : u8 + { + Identity, + Rotate90Clockwise, + Rotate180Clockwise, + Rotate270Clockwise, + }; + Type type = Type::Surfaceless; GPUTexture::Format surface_format = GPUTexture::Format::Unknown; + PreRotation surface_prerotation = PreRotation::Identity; u16 surface_width = 0; u16 surface_height = 0; float surface_refresh_rate = 0.0f; @@ -35,5 +44,24 @@ struct WindowInfo ALWAYS_INLINE bool IsSurfaceless() const { return type == Type::Surfaceless; } + ALWAYS_INLINE u32 GetPostRotatedWidth() const + { + return ShouldSwapDimensionsForPreRotation(surface_prerotation) ? surface_height : surface_width; + } + ALWAYS_INLINE u32 GetPostRotatedHeight() const + { + return ShouldSwapDimensionsForPreRotation(surface_prerotation) ? surface_width : surface_height; + } + + ALWAYS_INLINE static bool ShouldSwapDimensionsForPreRotation(PreRotation prerotation) + { + return (prerotation == PreRotation::Rotate90Clockwise || prerotation == PreRotation::Rotate270Clockwise); + } + + /// Sets a new pre-rotation, adjusting the virtual width/height to suit. + void SetPreRotated(PreRotation prerotation); + + static float GetZRotationForPreRotation(PreRotation prerotation); + static std::optional QueryRefreshRateForWindow(const WindowInfo& wi, Error* error = nullptr); };