Skip to content

Conversation

chachako
Copy link

This PR fixes screen tearing that occurs when resizing or rotating the display on Android, particularly noticeable when video is paused.

Problem

When the device orientation changes, the surface size updates but the native window buffer geometry is not synchronized, causing visual artifacts and screen tearing.

This issue was reported in mpv-android/mpv-android#991.

Testing

Tested on Android devices by:

  1. Playing a video and pausing it
  2. Rotating the device or change the video size

Before:

Screenrecorder-2025-07-13-21-20-25-249.mp4

After:

Screenrecorder-2025-07-14-01-21-47-889.mp4

@sfan5 sfan5 self-requested a review July 13, 2025 17:37
Copy link

github-actions bot commented Jul 13, 2025

@chachako
Copy link
Author

How does it look now? I've switched to using the swapchain abstraction

ra_gl_ctx_resize(ctx->swapchain, w, h, 0);

// Force a buffer swap to sync the new geometry
// This ensures the surface is updated even when paused
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks better but I'm still wondering about the paused case.
In my mental model the VO should be redrawing anyway, since the old frame will mismatch the new dimensions.

I'll try to give the code changes some testing later.

@sfan5 sfan5 self-requested a review July 16, 2025 21:59
@obscenelyvague
Copy link

obscenelyvague commented Jul 17, 2025

Works where it works but results in black screen on some ancient opengl versions like such, breaks rendering entirely

[ 140.114][v][vo/gpu-next/opengl] EGL_VERSION=1.4 Android META-EGL
[ 140.114][v][vo/gpu-next/opengl] EGL_VENDOR=Android
[ 140.114][v][vo/gpu-next/opengl] EGL_CLIENT_APIS=OpenGL_ES
[ 140.114][v][vo/gpu-next/opengl] Trying to create GLES 2.x + context.
[ 140.114][d][vo/gpu-next/opengl] Chosen EGLConfig:
[ 140.114][d][vo/gpu-next/opengl]   EGL_CONFIG_ID=0x9
[ 140.114][d][vo/gpu-next/opengl]   EGL_RED_SIZE=0x8
[ 140.114][d][vo/gpu-next/opengl]   EGL_GREEN_SIZE=0x8
[ 140.114][d][vo/gpu-next/opengl]   EGL_BLUE_SIZE=0x8
[ 140.114][d][vo/gpu-next/opengl]   EGL_ALPHA_SIZE=0x0
[ 140.115][d][vo/gpu-next/opengl]   EGL_COLOR_BUFFER_TYPE=0x308e
[ 140.115][d][vo/gpu-next/opengl]   EGL_CONFIG_CAVEAT=0x3038
[ 140.115][d][vo/gpu-next/opengl]   EGL_CONFORMANT=0x45
[ 140.115][d][vo/gpu-next/opengl]   EGL_NATIVE_VISUAL_ID=0x2
[ 140.117][v][vo/gpu-next/opengl] GL_VERSION='OpenGL ES 3.1 v1.r7p0-02rel0.4ea33e6671ff828e33c99c982634cd65'
[ 140.118][v][vo/gpu-next/opengl] Detected GLES 3.1.
[ 140.118][v][vo/gpu-next/opengl] GL_VENDOR='ARM'
[ 140.118][v][vo/gpu-next/opengl] GL_RENDERER='Mali-T720'
[ 140.118][v][vo/gpu-next/opengl] GL_SHADING_LANGUAGE_VERSION='OpenGL ES GLSL ES 3.10'

@sfan5
Copy link
Member

sfan5 commented Jul 18, 2025

Test case: opening a picture in mpv-android (paused) and switching the orientation
I tested this on my device and couldn't find any problem with it. It works and fixes the bug, though I am unable to test Vulkan on my phone.

Setting want_redraw is actually pointless since the VO code already sets its on its own (see the code that handles VOCTRL_EXTERNAL_RESIZE).

I tested removing the part of the code that seemed weird to me and it still works perfectly fine:

diff --git a/video/out/opengl/context_android.c b/video/out/opengl/context_android.c
--- a/video/out/opengl/context_android.c
+++ b/video/out/opengl/context_android.c
@@ -111,25 +111,12 @@ static bool android_reconfig(struct ra_ctx *ctx)
 
     // Update native window buffer geometry to prevent screen tearing
     ANativeWindow *native_window = vo_android_native_window(ctx->vo);
-    if (native_window) {
+    if (native_window)
         ANativeWindow_setBuffersGeometry(native_window, w, h, 0);
-    }
 
     ctx->vo->dwidth = w;
     ctx->vo->dheight = h;
     ra_gl_ctx_resize(ctx->swapchain, w, h, 0);
-    
-    // Force a buffer swap to sync the new geometry
-    // This ensures the surface is updated even when paused
-    struct priv *p = ctx->priv;
-    if (p->egl_display && p->egl_surface) {
-        struct ra_fbo fbo;
-        if (ctx->swapchain->fns->start_frame(ctx->swapchain, &fbo)) {
-            // Submit an empty frame to force buffer synchronization
-            ctx->swapchain->fns->submit_frame(ctx->swapchain, NULL);
-            ctx->swapchain->fns->swap_buffers(ctx->swapchain);
-        }
-    }
 
     return true;
 }

@chachako
Copy link
Author

Thanks, but I remember this didn't work on my device before. I'll retest and confirm later.

@obscenelyvague
Copy link

Simple fix is enough but it's also enough to cause breakage on older versions. Format being null may be why but idk

@obscenelyvague
Copy link

For all we know this is a driver issue affecting a very small subset of old devices. Safe to assume not many of them are in use today. I'm okay with this being merged as is and seeing if anyone else complains. Helps more devices actively being used and beats having to do client sided hacks to workaround the bug.

@obscenelyvague
Copy link

obscenelyvague commented Jul 21, 2025

Actually no, there's something seriously wrong with the approach of calling ANativeWindow_setBuffersGeometry like this. It's making mpv pick 6-bit as the display depth, which is not correct.

@sfan5
Copy link
Member

sfan5 commented Jul 21, 2025

It's possible that calling ANativeWindow_setBuffersGeometry like this resets the format of the window, previously set here:

eglGetConfigAttrib(p->egl_display, config, EGL_NATIVE_VISUAL_ID, &format);
ANativeWindow_setBuffersGeometry(native_window, 0, 0, format);

@sfan5 sfan5 removed their request for review July 26, 2025 18:14
@sfan5
Copy link
Member

sfan5 commented Aug 31, 2025

@chachako any time to look at this again?

@chachako
Copy link
Author

chachako commented Sep 2, 2025

@chachako any time to look at this again?

Sorry I've been a bit busy lately, I'll take a look again in the next day or two 🥹

@chachako
Copy link
Author

chachako commented Oct 1, 2025

@sfan5 Sorry for the wait! I tried the simplified approach you suggested, but unfortunately it doesn't work on my device.

I think I may have missed mentioning the most important detail earlier: this issue specifically occurs when the activity has android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize" in the AndroidManifest.xml.

Also, Some alternative workarounds:

  • Triggering set property glsl-shaders "" after android-surface-size changes
  • Making the player seek slightly also resolves it

@sfan5
Copy link
Member

sfan5 commented Oct 1, 2025

I think I may have missed mentioning the most important detail earlier: this issue specifically occurs when the activity has android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize" in the AndroidManifest.xml.

mpv-android has that (and more):
https://github.com/mpv-android/mpv-android/blob/ed47fb1cd443472a0b24bd76aea40cd06c77fc5b/app/src/main/AndroidManifest.xml#L38

Are you testing the changes with your own app or with mpv-android?
I'm thinking if the simplified approach works with mpv-android then you should be able to modify your app to work with it too, and it's good to go in general.

@chachako
Copy link
Author

chachako commented Oct 1, 2025

Are you testing the changes with your own app or with mpv-android?

I only tested the changes in this PR on my own app.

mpv-android certainly has its problems, but I haven't replaced and tested the mpv native it uses

Screenrecorder-2025-10-01-15-13-23-481.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants