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 _getSurface; private readonly Func _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(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 #include #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(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::ANATIVEWINDOW_TRANSFORM_MIRROR_HORIZONTAL | + ANATIVEWINDOW_TRANSFORM_ROTATE_90); + break; + case 0x40: + nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_MIRROR_VERTICAL; + break; + case 0x80: + nativeTransform = static_cast( + 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(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