From ae9f1a075c3a5f5bd0425828b6144f97265d8794 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 13 Jul 2023 18:17:59 +0800 Subject: [PATCH] Improve workaround for partial texture updates on Android * java/AndroidManifest.xml.in: * java/org/gnu/emacs/EmacsDialog.java (toAlertDialog): Don't change hardware acceleration state. * java/org/gnu/emacs/EmacsNative.java (notifyPixelsChanged): New function. * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView): New field `bitmapChanged'. (copyToFrontBuffer): Signal that the bitmap has changed. (onDraw): If the bitmap has changed, increment the generation ID. * src/android.c (JNICALL): Implement new function. --- java/AndroidManifest.xml.in | 5 -- java/org/gnu/emacs/EmacsDialog.java | 11 ----- java/org/gnu/emacs/EmacsNative.java | 5 ++ java/org/gnu/emacs/EmacsSurfaceView.java | 61 ++++++++++++++++-------- src/android.c | 17 +++++++ 5 files changed, 63 insertions(+), 36 deletions(-) diff --git a/java/AndroidManifest.xml.in b/java/AndroidManifest.xml.in index f2aede7369c..e79fb4e46e7 100644 --- a/java/AndroidManifest.xml.in +++ b/java/AndroidManifest.xml.in @@ -77,14 +77,10 @@ along with GNU Emacs. If not, see . --> @ANDROID_SHARED_USER_ID@ android:extractNativeLibs="true"> - - @@ -177,7 +173,6 @@ along with GNU Emacs. If not, see . --> = Build.VERSION_CODES.HONEYCOMB) - { - flag = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; - window = dialog.getWindow (); - window.addFlags (flag); - } - return dialog; } diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index 5b8b0f1eae5..1331539879a 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -249,6 +249,11 @@ public final class EmacsNative public static native void blitRect (Bitmap src, Bitmap dest, int x1, int y1, int x2, int y2); + /* Increment the generation ID of the specified BITMAP, forcing its + texture to be re-uploaded to the GPU. */ + + public static native void notifyPixelsChanged (Bitmap bitmap); + static { /* Older versions of Android cannot link correctly with shared diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java index 54fe70e1634..c47696b35c0 100644 --- a/java/org/gnu/emacs/EmacsSurfaceView.java +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -42,6 +42,10 @@ public final class EmacsSurfaceView extends View /* The complete buffer contents at the time of the last draw. */ private Bitmap frontBuffer; + /* Whether frontBuffer has been updated since the last call to + `onDraw'. */ + private boolean bitmapChanged; + /* Canvas representing the front buffer. */ private Canvas bitmapCanvas; @@ -105,6 +109,9 @@ public final class EmacsSurfaceView extends View bitmap.getWidth (), bitmap.getHeight ()); } + + /* See the large comment inside `onDraw'. */ + bitmapChanged = true; } private void @@ -176,27 +183,41 @@ public final class EmacsSurfaceView extends View onDraw (Canvas canvas) { /* Paint the view's bitmap; the bitmap might be recycled right - now. - - Hardware acceleration is disabled in AndroidManifest.xml to - prevent Android from uploading the front buffer to the GPU from - a separate thread. This is important for two reasons: first, - the GPU command queue uses a massive amount of memory (dozens - of MiB) to upload bitmaps to the GPU, regardless of how much of - the bitmap has actually changed. - - Secondly, asynchronous texturization leads to race conditions - when a buffer swap occurs before the front buffer is fully - uploaded to the GPU. Normally, only slight and tolerable - tearing should result from this behavior, but Android does not - properly interlock the ``generation ID'' used to avoid - texturizing unchanged bitmaps with the bitmap contents, - consequentially leading to textures in an incomplete state - remaining in use to the GPU if a buffer swap happens between - the image data being uploaded and the ``generation ID'' being - read. */ + now. */ if (frontBuffer != null) - canvas.drawBitmap (frontBuffer, 0f, 0f, uiThreadPaint); + { + /* The first time the bitmap is drawn after a buffer swap, + mark its contents as having changed. This increments the + ``generation ID'' used by Android to avoid uploading buffer + textures for unchanged bitmaps. + + When a buffer swap takes place, the bitmap is initially + updated from the Emacs thread, resulting in the generation + ID being increased. If the render thread is texturizing + the bitmap while the swap takes place, it might record the + generation ID after the update for a texture containing the + contents of the bitmap prior to the swap, leaving the + texture tied to the bitmap partially updated. + + Android never calls `onDraw' if the render thread is still + processing the bitmap. Update the generation ID here to + ensure that a new texture will be uploaded if the bitmap + has changed. + + Uploading the bitmap contents to the GPU uses an excessive + amount of memory, as the entire bitmap is placed into the + graphics command queue, but this memory is actually shared + among all other applications and reclaimed by the system + when necessary. */ + + if (bitmapChanged) + { + EmacsNative.notifyPixelsChanged (frontBuffer); + bitmapChanged = false; + } + + canvas.drawBitmap (frontBuffer, 0f, 0f, uiThreadPaint); + } } }; diff --git a/src/android.c b/src/android.c index 5eb6f65419c..f8ad78a2b39 100644 --- a/src/android.c +++ b/src/android.c @@ -3133,6 +3133,23 @@ NATIVE_NAME (blitRect) (JNIEnv *env, jobject object, AndroidBitmap_unlockPixels (env, src); } +JNIEXPORT void JNICALL +NATIVE_NAME (notifyPixelsChanged) (JNIEnv *env, jobject object, + jobject bitmap) +{ + void *data; + + /* Lock and unlock the bitmap. This calls + SkBitmap->notifyPixelsChanged. */ + + if (AndroidBitmap_lockPixels (env, bitmap, &data) < 0) + /* The return value is less than 0 if an error occurs. + Good luck finding this in the documentation. */ + return; + + AndroidBitmap_unlockPixels (env, bitmap); +} + /* Forward declarations of deadlock prevention functions. */ static void android_begin_query (void); -- 2.39.2