diff --git a/Common/GPU/Vulkan/VulkanContext.cpp b/Common/GPU/Vulkan/VulkanContext.cpp index f55b3a5a72..97a015324f 100644 --- a/Common/GPU/Vulkan/VulkanContext.cpp +++ b/Common/GPU/Vulkan/VulkanContext.cpp @@ -1556,6 +1556,21 @@ bool VulkanContext::InitSwapchain(VkPresentModeKHR desiredPresentMode) { } } +#if PPSSPP_PLATFORM(ANDROID) + // After an in-place rotation, the surface can report the new transform together with the previous + // orientation's extent. A Redmi 9A (PowerVR, Android 11) keeps doing it after surfaceChanged has given us the + // new size, so the swapchain stays wrong until the next rotation. The latest window size from surfaceChanged + // is current (g_display can lag behind it, until the render thread applies the resize), so if the extent is + // exactly that transposed, it's stale. + const VkExtent2D windowSize = cbGetDrawSize_ ? cbGetDrawSize_() : VkExtent2D{}; + if (windowSize.width > 0 && windowSize.height > 0 && windowSize.width != windowSize.height && + currentExtent.width == windowSize.height && currentExtent.height == windowSize.width) { + WARN_LOG(Log::G3D, "Surface extent %dx%d is the window transposed, probably stale after a rotation. Using %dx%d.", + currentExtent.width, currentExtent.height, windowSize.width, windowSize.height); + std::swap(currentExtent.width, currentExtent.height); + } +#endif + swapChainExtent_.width = clamp(currentExtent.width, surfCapabilities_.minImageExtent.width, surfCapabilities_.maxImageExtent.width); swapChainExtent_.height = clamp(currentExtent.height, surfCapabilities_.minImageExtent.height, surfCapabilities_.maxImageExtent.height); diff --git a/Common/GPU/Vulkan/VulkanContext.h b/Common/GPU/Vulkan/VulkanContext.h index e9dd3cc647..cd9b54b61e 100644 --- a/Common/GPU/Vulkan/VulkanContext.h +++ b/Common/GPU/Vulkan/VulkanContext.h @@ -524,6 +524,7 @@ private: // that we really don't want in everything that uses VulkanContext. void *winsysData1_ = nullptr; void *winsysData2_ = nullptr; + // The window's current size in pixels, from the platform. Optional. std::function cbGetDrawSize_; VkInstance instance_ = VK_NULL_HANDLE; diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 587140580b..1324aebeed 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -89,6 +89,7 @@ struct JNIEnv {}; #include "Common/VR/PPSSPPVR.h" #include "Common/GPU/Vulkan/VulkanLoader.h" #include "Common/GPU/GraphicsContext.h" +#include "Common/GPU/Vulkan/VulkanContext.h" #include "Common/GPU/Vulkan/VulkanGraphicsContext.h" #include "android/jni/AndroidEGLGraphicsContext.h" #include "Common/StringUtils.h" @@ -202,6 +203,10 @@ static void SetPermissionStatusFromJava(std::string_view name, PermissionStatus static GraphicsContext *graphicsContext; +// The latest surface size from backbufferResize, width in the high half, for Vulkan to check the surface's extent +// against. Atomic rather than under g_pendingResizeLock (below), since a resize applied directly can recreate the swapchain. +static std::atomic g_surfaceSize; + #if PPSSPP_ARCH(ARMV7) // Old Android workaround extern "C" { @@ -730,6 +735,10 @@ retry: delete ctx; goto retry; } + ((VulkanContext *)ctx->GetAPIContext())->SetCbGetDrawSize([]() { + const uint64_t size = g_surfaceSize; + return VkExtent2D{ (uint32_t)(size >> 32), (uint32_t)size }; + }); graphicsContext = ctx; // Now, we wait until we get a surface to continue initializing Vulkan. break; @@ -908,6 +917,8 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_backbufferResize(JNIEnv const bool rotated = lastRotation != -1 && rotation != lastRotation; lastRotation = rotation; + g_surfaceSize = ((uint64_t)(uint32_t)pixel_xres << 32) | (uint32_t)pixel_yres; + // pixel_*res is the backbuffer resolution. if (IsVREnabled()) { GetVRResolutionPerEye(&pixel_xres, &pixel_yres);