From 7a2fadb4997f7007764da5541d414668a90a8e1e Mon Sep 17 00:00:00 2001
From: Emmanuel Hansen <emmausssss@gmail.com>
Date: Tue, 8 Aug 2023 16:06:35 +0000
Subject: [PATCH] some optimizations. apply current transform to native window
 instead of defaulting to Identity

---
 src/LibRyujinx/Android/JniExportedMethods.cs  |   7 +-
 src/LibRyujinx/LibRyujinx.Graphics.cs         |  17 +-
 src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs |   2 +
 src/Ryujinx.Graphics.Vulkan/Window.cs         |   4 +-
 src/Ryujinx.Graphics.Vulkan/WindowBase.cs     |   3 +
 .../app/src/main/cpp/native_window.h          | 305 ++++++++++++++++++
 src/RyujinxAndroid/app/src/main/cpp/ryuijnx.h |   1 +
 .../app/src/main/cpp/ryujinx.cpp              |  98 ++++--
 .../main/java/org/ryujinx/android/GameHost.kt |  10 +-
 .../java/org/ryujinx/android/NativeHelpers.kt |  17 +-
 .../java/org/ryujinx/android/NativeWindow.kt  |  42 +++
 .../java/org/ryujinx/android/RyujinxNative.kt |   2 +-
 12 files changed, 473 insertions(+), 35 deletions(-)
 create mode 100644 src/RyujinxAndroid/app/src/main/cpp/native_window.h
 create mode 100644 src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeWindow.kt

diff --git a/src/LibRyujinx/Android/JniExportedMethods.cs b/src/LibRyujinx/Android/JniExportedMethods.cs
index 7e6351421..ce48aac06 100644
--- a/src/LibRyujinx/Android/JniExportedMethods.cs
+++ b/src/LibRyujinx/Android/JniExportedMethods.cs
@@ -30,6 +30,7 @@ namespace LibRyujinx
     {
         private static ManualResetEvent _surfaceEvent;
         private static long _surfacePtr;
+        private static long _window = 0;
 
         public static VulkanLoader? VulkanLoader { get; private set; }
 
@@ -48,6 +49,9 @@ namespace LibRyujinx
         [DllImport("libryujinxjni")]
         internal extern static void onFrameEnd(double time);
 
+        [DllImport("libryujinxjni")]
+        internal extern static void setCurrentTransform(long native_window, int transform);
+
         public delegate IntPtr JniCreateSurface(IntPtr native_surface, IntPtr instance);
 
         [UnmanagedCallersOnly(EntryPoint = "JNI_OnLoad")]
@@ -236,9 +240,10 @@ namespace LibRyujinx
         }
 
         [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsSetSurface")]
-        public static void JniSetSurface(JEnvRef jEnv, JObjectLocalRef jObj, JLong surfacePtr)
+        public static void JniSetSurface(JEnvRef jEnv, JObjectLocalRef jObj, JLong surfacePtr, JLong window)
         {
             _surfacePtr = surfacePtr;
+            _window = window;
 
             _surfaceEvent.Set();
         }
diff --git a/src/LibRyujinx/LibRyujinx.Graphics.cs b/src/LibRyujinx/LibRyujinx.Graphics.cs
index 3acd41d39..6308a94c3 100644
--- a/src/LibRyujinx/LibRyujinx.Graphics.cs
+++ b/src/LibRyujinx/LibRyujinx.Graphics.cs
@@ -185,7 +185,22 @@ namespace LibRyujinx
 
                     while (device.ConsumeFrameAvailable())
                     {
-                        device.PresentFrame(() => _swapBuffersCallback?.Invoke());
+                        device.PresentFrame(() =>
+                        {
+                            VulkanRenderer? vk = device.Gpu.Renderer as VulkanRenderer;
+                            if(vk == null)
+                            {
+                                vk = (device.Gpu.Renderer as ThreadedRenderer)?.BaseRenderer as VulkanRenderer;
+                            }
+
+                            if(vk != null)
+                            {
+                                var transform = vk.CurrentTransform;
+
+                                setCurrentTransform(_window, (int)transform);
+                            }
+                            _swapBuffersCallback?.Invoke();
+                        });
                     }
                 }
 
diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
index 1eff7e0cd..3b9b30340 100644
--- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
+++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
@@ -72,6 +72,8 @@ namespace Ryujinx.Graphics.Vulkan
 
         public IWindow Window => _window;
 
+        public SurfaceTransformFlagsKHR CurrentTransform => _window.CurrentTransform;
+
         private readonly Func<Instance, Vk, SurfaceKHR> _getSurface;
         private readonly Func<string[]> _getRequiredExtensions;
         private readonly string _preferredGpuId;
diff --git a/src/Ryujinx.Graphics.Vulkan/Window.cs b/src/Ryujinx.Graphics.Vulkan/Window.cs
index 347167e43..470c22347 100644
--- a/src/Ryujinx.Graphics.Vulkan/Window.cs
+++ b/src/Ryujinx.Graphics.Vulkan/Window.cs
@@ -132,6 +132,8 @@ namespace Ryujinx.Graphics.Vulkan
 
             var oldSwapchain = _swapchain;
 
+            CurrentTransform = capabilities.CurrentTransform;
+
             var swapchainCreateInfo = new SwapchainCreateInfoKHR
             {
                 SType = StructureType.SwapchainCreateInfoKhr,
@@ -143,7 +145,7 @@ namespace Ryujinx.Graphics.Vulkan
                 ImageUsage = ImageUsageFlags.ColorAttachmentBit | ImageUsageFlags.TransferDstBit | ImageUsageFlags.StorageBit,
                 ImageSharingMode = SharingMode.Exclusive,
                 ImageArrayLayers = 1,
-                PreTransform = Ryujinx.Common.SystemInfo.SystemInfo.IsAndroid() ? SurfaceTransformFlagsKHR.IdentityBitKhr : capabilities.CurrentTransform,
+                PreTransform = capabilities.CurrentTransform,
                 CompositeAlpha = ChooseCompositeAlpha(capabilities.SupportedCompositeAlpha),
                 PresentMode = ChooseSwapPresentMode(presentModes, _vsyncEnabled),
                 Clipped = true,
diff --git a/src/Ryujinx.Graphics.Vulkan/WindowBase.cs b/src/Ryujinx.Graphics.Vulkan/WindowBase.cs
index edb9c688c..f8c7d053a 100644
--- a/src/Ryujinx.Graphics.Vulkan/WindowBase.cs
+++ b/src/Ryujinx.Graphics.Vulkan/WindowBase.cs
@@ -1,4 +1,5 @@
 using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
 using System;
 
 namespace Ryujinx.Graphics.Vulkan
@@ -7,6 +8,8 @@ namespace Ryujinx.Graphics.Vulkan
     {
         public bool ScreenCaptureRequested { get; set; }
 
+        public SurfaceTransformFlagsKHR CurrentTransform { get; set; }
+
         public abstract void Dispose();
         public abstract void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback);
         public abstract void SetSize(int width, int height);
diff --git a/src/RyujinxAndroid/app/src/main/cpp/native_window.h b/src/RyujinxAndroid/app/src/main/cpp/native_window.h
new file mode 100644
index 000000000..354cd6ccd
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/cpp/native_window.h
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
+// Copyright © 2021 The Android Open Source Project
+
+#pragma once
+
+/* A collection of various types from AOSP that allow us to access private APIs for Native Window which we utilize for emulating the guest SF more accurately */
+
+/**
+ * @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativebase/include/nativebase/nativebase.h;l=29;drc=cb496acbe593326e8d5d563847067d02b2df40ec
+ */
+#define ANDROID_NATIVE_UNSIGNED_CAST(x) static_cast<unsigned int>(x)
+
+/**
+ * @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativebase/include/nativebase/nativebase.h;l=34-38;drc=cb496acbe593326e8d5d563847067d02b2df40ec
+ */
+#define ANDROID_NATIVE_MAKE_CONSTANT(a, b, c, d)  \
+    ((ANDROID_NATIVE_UNSIGNED_CAST(a) << 24) | \
+     (ANDROID_NATIVE_UNSIGNED_CAST(b) << 16) | \
+     (ANDROID_NATIVE_UNSIGNED_CAST(c) <<  8) | \
+     (ANDROID_NATIVE_UNSIGNED_CAST(d)))
+
+/**
+ * @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativewindow/include/system/window.h;l=60;drc=401cda638e7d17f6697b5a65c9a5ad79d056202d
+ */
+#define ANDROID_NATIVE_WINDOW_MAGIC     ANDROID_NATIVE_MAKE_CONSTANT('_','w','n','d')
+
+constexpr int AndroidNativeWindowMagic{ANDROID_NATIVE_WINDOW_MAGIC};
+
+#undef ANDROID_NATIVE_WINDOW_MAGIC
+#undef ANDROID_NATIVE_MAKE_CONSTANT
+#undef ANDROID_NATIVE_UNSIGNED_CAST
+
+/**
+ * @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativewindow/include/system/window.h;l=325-331;drc=401cda638e7d17f6697b5a65c9a5ad79d056202d
+ */
+constexpr int64_t NativeWindowTimestampAuto{-9223372036854775807LL - 1};
+
+/**
+ * @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativewindow/include/system/window.h;l=198-259;drc=401cda638e7d17f6697b5a65c9a5ad79d056202d
+ */
+enum {
+    NATIVE_WINDOW_CONNECT = 1,   /* deprecated */
+    NATIVE_WINDOW_DISCONNECT = 2,   /* deprecated */
+    NATIVE_WINDOW_SET_CROP = 3,   /* private */
+    NATIVE_WINDOW_SET_BUFFER_COUNT = 4,
+    NATIVE_WINDOW_SET_BUFFERS_TRANSFORM = 6,
+    NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP = 7,
+    NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS = 8,
+    NATIVE_WINDOW_SET_SCALING_MODE = 10,   /* private */
+    NATIVE_WINDOW_LOCK = 11,   /* private */
+    NATIVE_WINDOW_UNLOCK_AND_POST = 12,   /* private */
+    NATIVE_WINDOW_API_CONNECT = 13,   /* private */
+    NATIVE_WINDOW_API_DISCONNECT = 14,   /* private */
+    NATIVE_WINDOW_SET_BUFFERS_USER_DIMENSIONS = 15,   /* private */
+    NATIVE_WINDOW_SET_POST_TRANSFORM_CROP = 16,   /* deprecated, unimplemented */
+    NATIVE_WINDOW_SET_BUFFERS_STICKY_TRANSFORM = 17,   /* private */
+    NATIVE_WINDOW_SET_SIDEBAND_STREAM = 18,
+    NATIVE_WINDOW_SET_BUFFERS_DATASPACE = 19,
+    NATIVE_WINDOW_SET_SURFACE_DAMAGE = 20,   /* private */
+    NATIVE_WINDOW_SET_SHARED_BUFFER_MODE = 21,
+    NATIVE_WINDOW_SET_AUTO_REFRESH = 22,
+    NATIVE_WINDOW_GET_REFRESH_CYCLE_DURATION = 23,
+    NATIVE_WINDOW_GET_NEXT_FRAME_ID = 24,
+    NATIVE_WINDOW_ENABLE_FRAME_TIMESTAMPS = 25,
+    NATIVE_WINDOW_GET_COMPOSITOR_TIMING = 26,
+    NATIVE_WINDOW_GET_FRAME_TIMESTAMPS = 27,
+    NATIVE_WINDOW_GET_WIDE_COLOR_SUPPORT = 28,
+    NATIVE_WINDOW_GET_HDR_SUPPORT = 29,
+    NATIVE_WINDOW_GET_CONSUMER_USAGE64 = 31,
+    NATIVE_WINDOW_SET_BUFFERS_SMPTE2086_METADATA = 32,
+    NATIVE_WINDOW_SET_BUFFERS_CTA861_3_METADATA = 33,
+    NATIVE_WINDOW_SET_BUFFERS_HDR10_PLUS_METADATA = 34,
+    NATIVE_WINDOW_SET_AUTO_PREROTATION = 35,
+    NATIVE_WINDOW_GET_LAST_DEQUEUE_START = 36,    /* private */
+    NATIVE_WINDOW_SET_DEQUEUE_TIMEOUT = 37,    /* private */
+    NATIVE_WINDOW_GET_LAST_DEQUEUE_DURATION = 38,    /* private */
+    NATIVE_WINDOW_GET_LAST_QUEUE_DURATION = 39,    /* private */
+    NATIVE_WINDOW_SET_FRAME_RATE = 40,
+    NATIVE_WINDOW_SET_CANCEL_INTERCEPTOR = 41,    /* private */
+    NATIVE_WINDOW_SET_DEQUEUE_INTERCEPTOR = 42,    /* private */
+    NATIVE_WINDOW_SET_PERFORM_INTERCEPTOR = 43,    /* private */
+    NATIVE_WINDOW_SET_QUEUE_INTERCEPTOR = 44,    /* private */
+    NATIVE_WINDOW_ALLOCATE_BUFFERS = 45,    /* private */
+    NATIVE_WINDOW_GET_LAST_QUEUED_BUFFER = 46,    /* private */
+    NATIVE_WINDOW_SET_QUERY_INTERCEPTOR = 47,    /* private */
+    NATIVE_WINDOW_GET_LAST_QUEUED_BUFFER2 = 50,    /* private */
+};
+
+/**
+ * @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativebase/include/nativebase/nativebase.h;l=43-56;drc=cb496acbe593326e8d5d563847067d02b2df40ec
+ */
+struct android_native_base_t {
+    int magic;
+    int version;
+    void *reserved[4];
+
+    void (*incRef)(android_native_base_t *);
+
+    void (*decRef)(android_native_base_t *);
+};
+
+/**
+ * @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativewindow/include/system/window.h;l=341-560;drc=401cda638e7d17f6697b5a65c9a5ad79d056202d
+ */
+struct ANativeWindow {
+    struct android_native_base_t common;
+
+    /* flags describing some attributes of this surface or its updater */
+    const uint32_t flags;
+
+    /* min swap interval supported by this updated */
+    const int minSwapInterval;
+
+    /* max swap interval supported by this updated */
+    const int maxSwapInterval;
+
+    /* horizontal and vertical resolution in DPI */
+    const float xdpi;
+    const float ydpi;
+
+    /* Some storage reserved for the OEM's driver. */
+    intptr_t oem[4];
+
+    /*
+     * Set the swap interval for this surface.
+     *
+     * Returns 0 on success or -errno on error.
+     */
+    int (*setSwapInterval)(struct ANativeWindow *window,
+                           int interval);
+
+    /*
+     * Hook called by EGL to acquire a buffer. After this call, the buffer
+     * is not locked, so its content cannot be modified. This call may block if
+     * no buffers are available.
+     *
+     * The window holds a reference to the buffer between dequeueBuffer and
+     * either queueBuffer or cancelBuffer, so clients only need their own
+     * reference if they might use the buffer after queueing or canceling it.
+     * Holding a reference to a buffer after queueing or canceling it is only
+     * allowed if a specific buffer count has been set.
+     *
+     * Returns 0 on success or -errno on error.
+     *
+     * XXX: This function is deprecated.  It will continue to work for some
+     * time for binary compatibility, but the new dequeueBuffer function that
+     * outputs a fence file descriptor should be used in its place.
+     */
+    int (*dequeueBuffer_DEPRECATED)(struct ANativeWindow *window,
+                                    struct ANativeWindowBuffer **buffer);
+
+    /*
+     * hook called by EGL to lock a buffer. This MUST be called before modifying
+     * the content of a buffer. The buffer must have been acquired with
+     * dequeueBuffer first.
+     *
+     * Returns 0 on success or -errno on error.
+     *
+     * XXX: This function is deprecated.  It will continue to work for some
+     * time for binary compatibility, but it is essentially a no-op, and calls
+     * to it should be removed.
+     */
+    int (*lockBuffer_DEPRECATED)(struct ANativeWindow *window,
+                                 struct ANativeWindowBuffer *buffer);
+
+    /*
+     * Hook called by EGL when modifications to the render buffer are done.
+     * This unlocks and post the buffer.
+     *
+     * The window holds a reference to the buffer between dequeueBuffer and
+     * either queueBuffer or cancelBuffer, so clients only need their own
+     * reference if they might use the buffer after queueing or canceling it.
+     * Holding a reference to a buffer after queueing or canceling it is only
+     * allowed if a specific buffer count has been set.
+     *
+     * Buffers MUST be queued in the same order than they were dequeued.
+     *
+     * Returns 0 on success or -errno on error.
+     *
+     * XXX: This function is deprecated.  It will continue to work for some
+     * time for binary compatibility, but the new queueBuffer function that
+     * takes a fence file descriptor should be used in its place (pass a value
+     * of -1 for the fence file descriptor if there is no valid one to pass).
+     */
+    int (*queueBuffer_DEPRECATED)(struct ANativeWindow *window,
+                                  struct ANativeWindowBuffer *buffer);
+
+    /*
+     * hook used to retrieve information about the native window.
+     *
+     * Returns 0 on success or -errno on error.
+     */
+    int (*query)(const struct ANativeWindow *window,
+                 int what, int *value);
+
+    /*
+     * hook used to perform various operations on the surface.
+     * (*perform)() is a generic mechanism to add functionality to
+     * ANativeWindow while keeping backward binary compatibility.
+     *
+     * DO NOT CALL THIS HOOK DIRECTLY.  Instead, use the helper functions
+     * defined below.
+     *
+     * (*perform)() returns -ENOENT if the 'what' parameter is not supported
+     * by the surface's implementation.
+     *
+     * See above for a list of valid operations, such as
+     * NATIVE_WINDOW_SET_USAGE or NATIVE_WINDOW_CONNECT
+     */
+    int (*perform)(struct ANativeWindow *window,
+                   int operation, ...);
+
+    /*
+     * Hook used to cancel a buffer that has been dequeued.
+     * No synchronization is performed between dequeue() and cancel(), so
+     * either external synchronization is needed, or these functions must be
+     * called from the same thread.
+     *
+     * The window holds a reference to the buffer between dequeueBuffer and
+     * either queueBuffer or cancelBuffer, so clients only need their own
+     * reference if they might use the buffer after queueing or canceling it.
+     * Holding a reference to a buffer after queueing or canceling it is only
+     * allowed if a specific buffer count has been set.
+     *
+     * XXX: This function is deprecated.  It will continue to work for some
+     * time for binary compatibility, but the new cancelBuffer function that
+     * takes a fence file descriptor should be used in its place (pass a value
+     * of -1 for the fence file descriptor if there is no valid one to pass).
+     */
+    int (*cancelBuffer_DEPRECATED)(struct ANativeWindow *window,
+                                   struct ANativeWindowBuffer *buffer);
+
+    /*
+     * Hook called by EGL to acquire a buffer. This call may block if no
+     * buffers are available.
+     *
+     * The window holds a reference to the buffer between dequeueBuffer and
+     * either queueBuffer or cancelBuffer, so clients only need their own
+     * reference if they might use the buffer after queueing or canceling it.
+     * Holding a reference to a buffer after queueing or canceling it is only
+     * allowed if a specific buffer count has been set.
+     *
+     * The libsync fence file descriptor returned in the int pointed to by the
+     * fenceFd argument will refer to the fence that must signal before the
+     * dequeued buffer may be written to.  A value of -1 indicates that the
+     * caller may access the buffer immediately without waiting on a fence.  If
+     * a valid file descriptor is returned (i.e. any value except -1) then the
+     * caller is responsible for closing the file descriptor.
+     *
+     * Returns 0 on success or -errno on error.
+     */
+    int (*dequeueBuffer)(struct ANativeWindow *window,
+                         struct ANativeWindowBuffer **buffer, int *fenceFd);
+
+    /*
+     * Hook called by EGL when modifications to the render buffer are done.
+     * This unlocks and post the buffer.
+     *
+     * The window holds a reference to the buffer between dequeueBuffer and
+     * either queueBuffer or cancelBuffer, so clients only need their own
+     * reference if they might use the buffer after queueing or canceling it.
+     * Holding a reference to a buffer after queueing or canceling it is only
+     * allowed if a specific buffer count has been set.
+     *
+     * The fenceFd argument specifies a libsync fence file descriptor for a
+     * fence that must signal before the buffer can be accessed.  If the buffer
+     * can be accessed immediately then a value of -1 should be used.  The
+     * caller must not use the file descriptor after it is passed to
+     * queueBuffer, and the ANativeWindow implementation is responsible for
+     * closing it.
+     *
+     * Returns 0 on success or -errno on error.
+     */
+    int (*queueBuffer)(struct ANativeWindow *window,
+                       struct ANativeWindowBuffer *buffer, int fenceFd);
+
+    /*
+     * Hook used to cancel a buffer that has been dequeued.
+     * No synchronization is performed between dequeue() and cancel(), so
+     * either external synchronization is needed, or these functions must be
+     * called from the same thread.
+     *
+     * The window holds a reference to the buffer between dequeueBuffer and
+     * either queueBuffer or cancelBuffer, so clients only need their own
+     * reference if they might use the buffer after queueing or canceling it.
+     * Holding a reference to a buffer after queueing or canceling it is only
+     * allowed if a specific buffer count has been set.
+     *
+     * The fenceFd argument specifies a libsync fence file decsriptor for a
+     * fence that must signal before the buffer can be accessed.  If the buffer
+     * can be accessed immediately then a value of -1 should be used.
+     *
+     * Note that if the client has not waited on the fence that was returned
+     * from dequeueBuffer, that same fence should be passed to cancelBuffer to
+     * ensure that future uses of the buffer are preceded by a wait on that
+     * fence.  The caller must not use the file descriptor after it is passed
+     * to cancelBuffer, and the ANativeWindow implementation is responsible for
+     * closing it.
+     *
+     * Returns 0 on success or -errno on error.
+     */
+    int (*cancelBuffer)(struct ANativeWindow *window,
+                        struct ANativeWindowBuffer *buffer, int fenceFd);
+};
diff --git a/src/RyujinxAndroid/app/src/main/cpp/ryuijnx.h b/src/RyujinxAndroid/app/src/main/cpp/ryuijnx.h
index b9573694c..b26687b44 100644
--- a/src/RyujinxAndroid/app/src/main/cpp/ryuijnx.h
+++ b/src/RyujinxAndroid/app/src/main/cpp/ryuijnx.h
@@ -18,6 +18,7 @@
 #include <cassert>
 #include <fcntl.h>
 #include "libraries/adrenotools/include/adrenotools/driver.h"
+#include "native_window.h"
 
 // A macro to pass call to Vulkan and check for return value for success
 #define CALL_VK(func)                                                 \
diff --git a/src/RyujinxAndroid/app/src/main/cpp/ryujinx.cpp b/src/RyujinxAndroid/app/src/main/cpp/ryujinx.cpp
index 12a331b9d..858fb9be0 100644
--- a/src/RyujinxAndroid/app/src/main/cpp/ryujinx.cpp
+++ b/src/RyujinxAndroid/app/src/main/cpp/ryujinx.cpp
@@ -52,7 +52,6 @@ extern "C"
             jobject instance,
             jobject surface) {
         auto nativeWindow = ANativeWindow_fromSurface(env, surface);
-
         return nativeWindow == NULL ? -1 : (jlong) nativeWindow;
     }
 
@@ -67,28 +66,6 @@ extern "C"
             ANativeWindow_release(nativeWindow);
     }
 
-JNIEXPORT jlong JNICALL
-Java_org_ryujinx_android_NativeHelpers_createSurface(
-        JNIEnv *env,
-        jobject instance,
-        jlong vulkanInstance,
-        jlong window) {
-    auto nativeWindow = (ANativeWindow *) window;
-
-    if (nativeWindow != NULL)
-        return -1;
-    VkSurfaceKHR surface;
-    auto vkInstance = VkInstance(vulkanInstance);
-    auto fpCreateAndroidSurfaceKHR =
-            reinterpret_cast<PFN_vkCreateAndroidSurfaceKHR>(vkGetInstanceProcAddr(vkInstance, "vkCreateAndroidSurfaceKHR"));
-    if (!fpCreateAndroidSurfaceKHR)
-        return -1;
-    VkAndroidSurfaceCreateInfoKHR info = { VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR };
-    info.window = nativeWindow;
-    VK_CHECK(fpCreateAndroidSurfaceKHR(vkInstance, &info, nullptr, &surface));
-    return (jlong)surface;
-}
-
 JNIEXPORT void JNICALL
 Java_org_ryujinx_android_NativeHelpers_attachCurrentThread(
         JNIEnv *env,
@@ -192,6 +169,54 @@ void onFrameEnd(double time) {
                               nano);
 }
 
+extern "C"
+void setCurrentTransform(long native_window, int transform){
+    if(native_window == 0 || native_window == -1)
+        return;
+    auto nativeWindow = (ANativeWindow *) native_window;
+
+    auto nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_IDENTITY;
+
+    transform = transform >> 1;
+
+    // transform is a valid VkSurfaceTransformFlagBitsKHR
+    switch (transform) {
+        case 0x1:
+            nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_IDENTITY;
+            break;
+        case 0x2:
+            nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_90;
+            break;
+        case 0x4:
+            nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_180;
+            break;
+        case 0x8:
+            nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_270;
+            break;
+        case 0x10:
+            nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_MIRROR_HORIZONTAL;
+            break;
+        case 0x20:
+            nativeTransform = static_cast<ANativeWindowTransform>(
+                    ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_MIRROR_HORIZONTAL |
+                    ANATIVEWINDOW_TRANSFORM_ROTATE_90);
+            break;
+        case 0x40:
+            nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_MIRROR_VERTICAL;
+            break;
+        case 0x80:
+            nativeTransform = static_cast<ANativeWindowTransform>(
+                    ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_MIRROR_VERTICAL |
+                    ANATIVEWINDOW_TRANSFORM_ROTATE_90);
+            break;
+        case 0x100:
+            nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_IDENTITY;
+            break;
+    }
+
+    nativeWindow->perform(nativeWindow, NATIVE_WINDOW_SET_BUFFERS_TRANSFORM, static_cast<int32_t>(nativeTransform));
+}
+
 extern "C"
 JNIEXPORT jlong JNICALL
 Java_org_ryujinx_android_NativeHelpers_loadDriver(JNIEnv *env, jobject thiz,
@@ -231,3 +256,30 @@ JNIEXPORT void JNICALL
 Java_org_ryujinx_android_NativeHelpers_setTurboMode(JNIEnv *env, jobject thiz, jboolean enable) {
     adrenotools_set_turbo(enable);
 }
+
+extern "C"
+JNIEXPORT jint JNICALL
+Java_org_ryujinx_android_NativeHelpers_getMaxSwapInterval(JNIEnv *env, jobject thiz,
+                                                       jlong native_window) {
+    auto nativeWindow = (ANativeWindow *) native_window;
+
+    return nativeWindow->maxSwapInterval;
+}
+
+extern "C"
+JNIEXPORT jint JNICALL
+Java_org_ryujinx_android_NativeHelpers_getMinSwapInterval(JNIEnv *env, jobject thiz,
+                                                          jlong native_window) {
+    auto nativeWindow = (ANativeWindow *) native_window;
+
+    return nativeWindow->minSwapInterval;
+}
+
+extern "C"
+JNIEXPORT jint JNICALL
+Java_org_ryujinx_android_NativeHelpers_setSwapInterval(JNIEnv *env, jobject thiz,
+                                                       jlong native_window, jint swap_interval) {
+    auto nativeWindow = (ANativeWindow *) native_window;
+
+    return nativeWindow->setSwapInterval(nativeWindow, swap_interval);
+}
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt
index 76bc2c9f2..8a15e3a05 100644
--- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt
@@ -21,11 +21,14 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
     private var _guestThread: Thread? = null
     private var _isInit: Boolean = false
     private var _isStarted: Boolean = false
+    private val nativeWindow : NativeWindow
 
     private var _nativeRyujinx: RyujinxNative = RyujinxNative()
 
     init {
         holder.addCallback(this)
+
+        nativeWindow = NativeWindow(this)
     }
 
     override fun surfaceCreated(holder: SurfaceHolder) {
@@ -38,9 +41,10 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
 
         if(_width != width || _height != height)
         {
-            val nativeHelpers = NativeHelpers()
-            val window = nativeHelpers.getNativeWindow(holder.surface)
-            _nativeRyujinx.graphicsSetSurface(window)
+            val window = nativeWindow.requeryWindowHandle()
+            _nativeRyujinx.graphicsSetSurface(window, nativeWindow.nativePointer)
+
+            nativeWindow.swapInterval = 0;
         }
 
         _width = width
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeHelpers.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeHelpers.kt
index f37114ad7..af46708a2 100644
--- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeHelpers.kt
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeHelpers.kt
@@ -9,14 +9,21 @@ class NativeHelpers {
             System.loadLibrary("ryujinxjni")
         }
     }
-    external fun releaseNativeWindow(window:Long)
-    external fun createSurface(vkInstance:Long, window:Long) : Long
-    external fun getCreateSurfacePtr() : Long
-    external fun getNativeWindow(surface:Surface) : Long
+
+    external fun releaseNativeWindow(window: Long)
+    external fun getCreateSurfacePtr(): Long
+    external fun getNativeWindow(surface: Surface): Long
     external fun attachCurrentThread()
     external fun detachCurrentThread()
 
-    external fun loadDriver(nativeLibPath:String, privateAppsPath:String, driverName:String) : Long
+    external fun loadDriver(
+        nativeLibPath: String,
+        privateAppsPath: String,
+        driverName: String
+    ): Long
 
     external fun setTurboMode(enable: Boolean)
+    external fun getMaxSwapInterval(nativeWindow: Long): Int
+    external fun getMinSwapInterval(nativeWindow: Long): Int
+    external fun setSwapInterval(nativeWindow: Long, swapInterval: Int): Int
 }
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeWindow.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeWindow.kt
new file mode 100644
index 000000000..74f96c058
--- /dev/null
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeWindow.kt
@@ -0,0 +1,42 @@
+package org.ryujinx.android
+
+import android.view.SurfaceView
+
+class NativeWindow(val surface: SurfaceView) {
+    var nativePointer: Long
+    var nativeHelpers: NativeHelpers = NativeHelpers()
+    private var _swapInterval : Int = 0
+
+    var maxSwapInterval : Int = 0
+        get() {
+            return if (nativePointer == -1L) 0 else nativeHelpers.getMaxSwapInterval(nativePointer)
+        }
+
+    var minSwapInterval : Int = 0
+        get() {
+            return if (nativePointer == -1L) 0 else nativeHelpers.getMinSwapInterval(nativePointer)
+        }
+
+    var swapInterval : Int
+        get() {
+            return _swapInterval
+        }
+        set(value) {
+            if(nativePointer == -1L ||  nativeHelpers.setSwapInterval(nativePointer, value) == 0)
+                _swapInterval = value
+        }
+
+    init {
+        nativePointer = nativeHelpers.getNativeWindow(surface.holder.surface);
+
+        swapInterval = maxOf(1, minSwapInterval)
+    }
+
+    fun requeryWindowHandle() : Long {
+        nativePointer = nativeHelpers.getNativeWindow(surface.holder.surface)
+
+        swapInterval = swapInterval
+
+        return nativePointer
+    }
+}
diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RyujinxNative.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RyujinxNative.kt
index 682050602..98e54e1e4 100644
--- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RyujinxNative.kt
+++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RyujinxNative.kt
@@ -47,7 +47,7 @@ class RyujinxNative {
     external fun inputSetButtonReleased(button: Int, id: Int)
     external fun inputConnectGamepad(index: Int): Int
     external fun inputSetStickAxis(stick: Int, x: Float, y: Float, id: Int)
-    external fun graphicsSetSurface(surface: Long)
+    external fun graphicsSetSurface(surface: Long, window: Long)
     external fun deviceCloseEmulation()
     external fun deviceSignalEmulationClose()
     external fun deviceGetDlcTitleId(path: String, ncaPath: String) : String