2
0
Эх сурвалжийг харах

[Android] Add support for setting the window resolution on Android (#16715)

* Add support for setting the window resolution on Android

-Add streching feature capability for RHI Vulkan Android
-Add window aspect ratio scaling for Android
-Add WindowSurfaceBus for requesting the WSISurface that a window is connected to
-Enable r_resolutionMode for Android

Signed-off-by: Akio Gaule <[email protected]>
Akio Gaule 1 жил өмнө
parent
commit
4f9baa3699

+ 3 - 1
Code/Framework/AzFramework/AzFramework/Windowing/NativeWindow.cpp

@@ -347,7 +347,9 @@ namespace AzFramework
             // after enabling customized resolution, send notification if the customized resolution is different than client area size
             if (enable)
             {
-                if (m_customizedRenderResolution != GetClientAreaSize())
+                if (m_customizedRenderResolution.m_width > 0 &&
+                    m_customizedRenderResolution.m_height > 0 &&
+                    m_customizedRenderResolution != GetClientAreaSize())
                 {
                     WindowNotificationBus::Event(GetWindowHandle(), &WindowNotificationBus::Events::OnResolutionChanged, m_customizedRenderResolution.m_width, m_customizedRenderResolution.m_height);
                 }

+ 31 - 1
Code/Framework/AzFramework/Platform/Android/AzFramework/Windowing/NativeWindow_Android.cpp

@@ -27,6 +27,9 @@ namespace AzFramework
                         const WindowStyleMasks& styleMasks) override;
         NativeWindowHandle GetWindowHandle() const override;
         uint32_t GetDisplayRefreshRate() const override;
+        void SetRenderResolution(WindowSize resolution) override;
+
+
     private:
         ANativeWindow* m_nativeWindow = nullptr;
     };
@@ -39,20 +42,23 @@ namespace AzFramework
 
         int windowWidth = 0;
         int windowHeight = 0;
+        // Window size is determinate by the OS. We cannot set it like in other systems.
         if (AZ::Android::Utils::GetWindowSize(windowWidth, windowHeight))
         {
-            // Use native window size from the device if available
+            // Use the size returned by the OS (most likely the fullscreen size).
             m_width = static_cast<uint32_t>(windowWidth);
             m_height = static_cast<uint32_t>(windowHeight);
         }
         else
         {
+            AZ_Error("NativeWindow", false, "Failed to get native window size");
             m_width = geometry.m_width;
             m_height = geometry.m_height;
         }
 
         if (m_nativeWindow)
         {
+            // Set the resolution to the same size of the window.
             ANativeWindow_setBuffersGeometry(m_nativeWindow, m_width, m_height, ANativeWindow_getFormat(m_nativeWindow));
         }
     }
@@ -69,6 +75,30 @@ namespace AzFramework
         return 60;
     }
 
+    void NativeWindowImpl_Android::SetRenderResolution(WindowSize resolution)
+    {
+        WindowSize newResolution = resolution;
+        if (m_enableCustomizedResolution && m_nativeWindow)
+        {
+            // Fit the aspect ratio of the resolution so it matches the aspect ratio of the window
+            // so when the window image is scaled by the OS compositor, it doesn't look stretched in any direction.
+            float aspectRatio = static_cast<float>(m_width) / m_height;
+            if (aspectRatio > 1.0f)
+            {
+                newResolution.m_width = AZStd::max(resolution.m_width, resolution.m_height);
+                newResolution.m_height = newResolution.m_width / aspectRatio;
+            }
+            else
+            {
+                newResolution.m_height = AZStd::max(resolution.m_width, resolution.m_height);
+                newResolution.m_width *= newResolution.m_height * aspectRatio;
+            }
+            ANativeWindow_setBuffersGeometry(
+                m_nativeWindow, newResolution.m_width, newResolution.m_height, ANativeWindow_getFormat(m_nativeWindow));
+        }
+        NativeWindow::Implementation::SetRenderResolution(newResolution);
+    }
+
     ////////////////////////////////////////////////////////////////////////////////////////////////
     class AndroidNativeWindowFactory 
         : public NativeWindow::ImplementationFactory

+ 20 - 14
Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp

@@ -280,20 +280,7 @@ namespace AZ
                     [this]()
                     {
                         Initialize();
-                        if (m_nativeWindow)
-                        {
-                            // wait until swapchain has been created before setting fullscreen state
-                            if (r_resolutionMode > 0u)
-                            {
-                                m_nativeWindow->SetEnableCustomizedResolution(true);
-                                m_nativeWindow->SetRenderResolution(AzFramework::WindowSize(r_width, r_height));
-                            }
-                            else
-                            {
-                                m_nativeWindow->SetEnableCustomizedResolution(false);
-                            }
-                            m_nativeWindow->SetFullScreenState(r_fullscreen);
-                        }
+                        SetWindowResolution();
                     });
             }
 
@@ -371,6 +358,7 @@ namespace AZ
                             CreateDefaultRenderPipeline();
                         }
                     }
+                    SetWindowResolution();
                 }
             }
 
@@ -413,6 +401,24 @@ namespace AZ
                 AzFramework::WindowNotificationBus::Handler::BusConnect(GetDefaultWindowHandle());
             }
 
+            void BootstrapSystemComponent::SetWindowResolution()
+            {
+                if (m_nativeWindow)
+                {
+                    // wait until swapchain has been created before setting fullscreen state
+                    if (r_resolutionMode > 0u)
+                    {
+                        m_nativeWindow->SetEnableCustomizedResolution(true);
+                        m_nativeWindow->SetRenderResolution(AzFramework::WindowSize(r_width, r_height));
+                    }
+                    else
+                    {
+                        m_nativeWindow->SetEnableCustomizedResolution(false);
+                    }
+                    m_nativeWindow->SetFullScreenState(r_fullscreen);
+                }
+            }
+
             AZ::RPI::ScenePtr BootstrapSystemComponent::GetOrCreateAtomSceneFromAzScene(AzFramework::Scene* scene)
             {
                 // Get or create a weak pointer to our scene

+ 1 - 0
Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.h

@@ -99,6 +99,7 @@ namespace AZ
                 void RemoveRenderPipeline();
 
                 void CreateViewportContext();
+                void SetWindowResolution();
 
                 //! Load a render pipeline from disk and add it to the scene
                 RPI::RenderPipelinePtr LoadPipeline(

+ 1 - 0
Gems/Atom/RHI/Vulkan/Code/Source/Platform/Android/Vulkan_Traits_Android.h

@@ -15,3 +15,4 @@
 #define AZ_TRAIT_ATOM_VULKAN_DISABLE_DUAL_SOURCE_BLENDING 1
 #define AZ_TRAIT_ATOM_VULKAN_LAYER_LUNARG_STD_VALIDATION_SUPPORT 0
 #define AZ_TRAIT_ATOM_VULKAN_RECREATE_SWAPCHAIN_WHEN_SUBOPTIMAL 0
+#define AZ_TRAIT_ATOM_VULKAN_SWAPCHAIN_SCALING_FLAGS AZ::RHI::ScalingFlags::Stretch

+ 1 - 0
Gems/Atom/RHI/Vulkan/Code/Source/Platform/Linux/Vulkan_Traits_Linux.h

@@ -14,3 +14,4 @@
 #define AZ_TRAIT_ATOM_MOBILE_AZSL_PLATFORM_HEADER "Builders/ShaderHeaders/Platform/Android/Vulkan/PlatformHeader.hlsli"
 #define AZ_TRAIT_ATOM_VULKAN_DISABLE_DUAL_SOURCE_BLENDING 0
 #define AZ_TRAIT_ATOM_VULKAN_RECREATE_SWAPCHAIN_WHEN_SUBOPTIMAL 1
+#define AZ_TRAIT_ATOM_VULKAN_SWAPCHAIN_SCALING_FLAGS AZ::RHI::ScalingFlags::None

+ 1 - 0
Gems/Atom/RHI/Vulkan/Code/Source/Platform/Mac/Vulkan_Traits_Mac.h

@@ -14,3 +14,4 @@
 #define AZ_TRAIT_ATOM_MOBILE_AZSL_PLATFORM_HEADER ""
 #define AZ_TRAIT_ATOM_VULKAN_DISABLE_DUAL_SOURCE_BLENDING 0
 #define AZ_TRAIT_ATOM_VULKAN_RECREATE_SWAPCHAIN_WHEN_SUBOPTIMAL 1
+#define AZ_TRAIT_ATOM_VULKAN_SWAPCHAIN_SCALING_FLAGS AZ::RHI::ScalingFlags::None

+ 1 - 0
Gems/Atom/RHI/Vulkan/Code/Source/Platform/Windows/Vulkan_Traits_Windows.h

@@ -14,3 +14,4 @@
 #define AZ_TRAIT_ATOM_MOBILE_AZSL_PLATFORM_HEADER "Builders/ShaderHeaders/Platform/Android/Vulkan/PlatformHeader.hlsli"
 #define AZ_TRAIT_ATOM_VULKAN_DISABLE_DUAL_SOURCE_BLENDING 0
 #define AZ_TRAIT_ATOM_VULKAN_RECREATE_SWAPCHAIN_WHEN_SUBOPTIMAL 1
+#define AZ_TRAIT_ATOM_VULKAN_SWAPCHAIN_SCALING_FLAGS AZ::RHI::ScalingFlags::None

+ 23 - 6
Gems/Atom/RHI/Vulkan/Code/Source/RHI/Device.cpp

@@ -30,6 +30,7 @@
 #include <RHI/Pipeline.h>
 #include <RHI/SwapChain.h>
 #include <RHI/WSISurface.h>
+#include <RHI/WindowSurfaceBus.h>
 #include <Vulkan_Traits_Platform.h>
 #include <Atom/RHI.Reflect/VkAllocator.h>
 
@@ -798,17 +799,32 @@ namespace AZ
         AZStd::vector<RHI::Format> Device::GetValidSwapChainImageFormats(const RHI::WindowHandle& windowHandle) const
         {
             AZStd::vector<RHI::Format> formatsList;
-            WSISurface::Descriptor surfaceDescriptor{windowHandle};
-            RHI::Ptr<WSISurface> surface = WSISurface::Create();
-            const RHI::ResultCode result = surface->Init(surfaceDescriptor);
-            if (result != RHI::ResultCode::Success)
+            // On some platforms (e.g. Android) we cannot create a new WSISurface if the window is already connected to one, so we first
+            // check if the window is connected to one.
+            VkSurfaceKHR vkSurface = VK_NULL_HANDLE;
+            WindowSurfaceRequestsBus::EventResult(vkSurface, windowHandle, &WindowSurfaceRequestsBus::Events::GetNativeSurface);
+            RHI::Ptr<WSISurface> surface;
+            if (vkSurface == VK_NULL_HANDLE)
+            {
+                // Window is not connected to a WSISurface, so we create a temporary one to be able to get the valid device surface formats.
+                WSISurface::Descriptor surfaceDescriptor{ windowHandle };
+                surface = WSISurface::Create();
+                const RHI::ResultCode result = surface->Init(surfaceDescriptor);
+                if (result == RHI::ResultCode::Success)
+                {
+                    vkSurface = surface->GetNativeSurface();
+                }
+            }
+
+            if (vkSurface == VK_NULL_HANDLE)
             {
                 return formatsList;
             }
+
             const auto& physicalDevice = static_cast<const PhysicalDevice&>(GetPhysicalDevice());
             uint32_t surfaceFormatCount = 0;
             AssertSuccess(GetContext().GetPhysicalDeviceSurfaceFormatsKHR(
-                physicalDevice.GetNativePhysicalDevice(), surface->GetNativeSurface(), &surfaceFormatCount, nullptr));
+                physicalDevice.GetNativePhysicalDevice(), vkSurface, &surfaceFormatCount, nullptr));
             if (surfaceFormatCount == 0)
             {
                 AZ_Assert(false, "Surface support no format.");
@@ -817,7 +833,7 @@ namespace AZ
 
             AZStd::vector<VkSurfaceFormatKHR> surfaceFormats(surfaceFormatCount);
             AssertSuccess(GetContext().GetPhysicalDeviceSurfaceFormatsKHR(
-                physicalDevice.GetNativePhysicalDevice(), surface->GetNativeSurface(), &surfaceFormatCount, surfaceFormats.data()));
+                physicalDevice.GetNativePhysicalDevice(), vkSurface, &surfaceFormatCount, surfaceFormats.data()));
 
             AZStd::set<RHI::Format> formats;
             for (const VkSurfaceFormatKHR& surfaceFormat : surfaceFormats)
@@ -1167,6 +1183,7 @@ namespace AZ
                     }
                 }
             }
+            m_features.m_swapchainScalingFlags = AZ_TRAIT_ATOM_VULKAN_SWAPCHAIN_SCALING_FLAGS;
 
             const auto& deviceLimits = physicalDevice.GetDeviceLimits();
             m_limits.m_maxImageDimension1D = deviceLimits.maxImageDimension1D;

+ 7 - 1
Gems/Atom/RHI/Vulkan/Code/Source/RHI/WSISurface.cpp

@@ -21,13 +21,19 @@ namespace AZ
         RHI::ResultCode WSISurface::Init(const Descriptor& descriptor)
         {
             m_descriptor = descriptor;
-            return BuildNativeSurface();
+            RHI::ResultCode result = BuildNativeSurface();
+            if (result == RHI::ResultCode::Success)
+            {
+                WindowSurfaceRequestsBus::Handler::BusConnect(descriptor.m_windowHandle);
+            }
+            return result;
         }
 
         WSISurface::~WSISurface()
         {
             if (m_nativeSurface != VK_NULL_HANDLE)
             {
+                WindowSurfaceRequestsBus::Handler::BusDisconnect(m_descriptor.m_windowHandle);
                 Instance& instance = Instance::GetInstance();
                 instance.GetContext().DestroySurfaceKHR(instance.GetNativeInstance(), m_nativeSurface, VkSystemAllocator::Get());
                 m_nativeSurface = VK_NULL_HANDLE;

+ 4 - 1
Gems/Atom/RHI/Vulkan/Code/Source/RHI/WSISurface.h

@@ -10,6 +10,7 @@
 #include <Atom_RHI_Vulkan_Platform.h>
 #include <Atom/RHI/Object.h>
 #include <Atom/RHI.Reflect/SwapChainDescriptor.h>
+#include <RHI/WindowSurfaceBus.h>
 
 namespace AZ
 {
@@ -19,6 +20,7 @@ namespace AZ
 
         class WSISurface 
             : public RHI::Object
+            , public WindowSurfaceRequestsBus::Handler
         {
             using Base = RHI::Object;
 
@@ -35,7 +37,8 @@ namespace AZ
             virtual RHI::ResultCode Init(const Descriptor& descriptor);
             ~WSISurface();
 
-            VkSurfaceKHR GetNativeSurface() const;
+            // WindowSurfaceRequestsBus::Handler overrides...
+            VkSurfaceKHR GetNativeSurface() const override;
 
         protected:
             WSISurface() = default;

+ 34 - 0
Gems/Atom/RHI/Vulkan/Code/Source/RHI/WindowSurfaceBus.h

@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <AzCore/base.h>
+#include <AzCore/EBus/EBus.h>
+#include <Atom/RHI.Reflect/SwapChainDescriptor.h>
+
+namespace AZ
+{
+    namespace Vulkan
+    {
+        //! For requesting the WSISurface of a Native Window
+        class WindowSurfaceRequests
+            : public AZ::EBusTraits
+        {
+        public:
+            static const AZ::EBusHandlerPolicy HandlerPolicy = EBusHandlerPolicy::Single;
+            static const AZ::EBusAddressPolicy AddressPolicy = EBusAddressPolicy::ById;
+            using BusIdType = RHI::WindowHandle;
+
+            //! Returns the Vulkan Surface that the window is connected to.
+            virtual VkSurfaceKHR GetNativeSurface() const = 0;
+        };
+
+        using WindowSurfaceRequestsBus = AZ::EBus<WindowSurfaceRequests>;
+    } // namespace Vulkan
+} // namespace AZ

+ 1 - 0
Gems/Atom/RHI/Vulkan/Code/atom_rhi_vulkan_private_common_files.cmake

@@ -159,4 +159,5 @@ set(FILES
     Source/RHI/RayTracingShaderTable.h
     Source/RHI/Conversion.cpp
     Source/RHI/Conversion.h
+    Source/RHI/WindowSurfaceBus.h
 )

+ 3 - 1
Registry/Platform/Android/window.setreg

@@ -2,7 +2,9 @@
     "O3DE": {
         "Autoexec": {
             "ConsoleCommands": {
-                "sys_PakLogInvalidFileAccess" : 1
+                "sys_PakLogInvalidFileAccess" : 1,
+                "r_fullscreen" : 1,
+                "r_resolutionMode" : 1
             }
         }
     }