From 260821d68c721c5d348c72e4e07388e10f6891d8 Mon Sep 17 00:00:00 2001 From: Julian Winkler Date: Sun, 22 Dec 2024 10:20:50 +0100 Subject: [PATCH] Bitmap: implement pixel buffer access For GPU textures, the GdkTextureDownloader will take care of format conversions, so the application never sees the actual format. If the application calls AndroidBitmap_unlockPixels(), the texture is converted into a GdkMemoryTexture and can be accessed zero copy. --- .../android_graphics_Bitmap.h | 8 +++ .../graphics/android_graphics_Bitmap.c | 14 ++++ src/api-impl/android/graphics/Bitmap.java | 58 ++++++++++++--- src/libandroid/bitmap.c | 71 ++++++++++++++++--- 4 files changed, 130 insertions(+), 21 deletions(-) diff --git a/src/api-impl-jni/generated_headers/android_graphics_Bitmap.h b/src/api-impl-jni/generated_headers/android_graphics_Bitmap.h index 2f7924db..cdcbb302 100644 --- a/src/api-impl-jni/generated_headers/android_graphics_Bitmap.h +++ b/src/api-impl-jni/generated_headers/android_graphics_Bitmap.h @@ -71,6 +71,14 @@ JNIEXPORT jlong JNICALL Java_android_graphics_Bitmap_native_1ref_1texture JNIEXPORT void JNICALL Java_android_graphics_Bitmap_native_1get_1pixels (JNIEnv *, jclass, jlong, jintArray, jint, jint, jint, jint, jint, jint); +/* + * Class: android_graphics_Bitmap + * Method: native_copy_to_buffer + * Signature: (JLjava/nio/Buffer;II)V + */ +JNIEXPORT void JNICALL Java_android_graphics_Bitmap_native_1copy_1to_1buffer + (JNIEnv *, jclass, jlong, jobject, jint, jint); + #ifdef __cplusplus } #endif diff --git a/src/api-impl-jni/graphics/android_graphics_Bitmap.c b/src/api-impl-jni/graphics/android_graphics_Bitmap.c index ffc00ef3..35031e5a 100644 --- a/src/api-impl-jni/graphics/android_graphics_Bitmap.c +++ b/src/api-impl-jni/graphics/android_graphics_Bitmap.c @@ -1,6 +1,7 @@ #include #include "../defines.h" +#include "../util.h" #include "../generated_headers/android_graphics_Bitmap.h" @@ -90,3 +91,16 @@ JNIEXPORT void JNICALL Java_android_graphics_Bitmap_native_1get_1pixels(JNIEnv * gdk_texture_download(texture, (guchar *)(array + offset), stride*4); (*env)->ReleaseIntArrayElements(env, pixels, array, 0); } + +JNIEXPORT void JNICALL Java_android_graphics_Bitmap_native_1copy_1to_1buffer(JNIEnv *env, jclass class, jlong texture_ptr, jobject buffer, jint memory_format, jint stride) +{ + GdkTexture *texture = GDK_TEXTURE(_PTR(texture_ptr)); + GdkTextureDownloader *downloader = gdk_texture_downloader_new(texture); + gdk_texture_downloader_set_format(downloader, memory_format); + jarray array_ref; + jbyte *array; + guchar *data = get_nio_buffer(env, buffer, &array_ref, &array); + gdk_texture_downloader_download_into(downloader, data, stride); + release_nio_buffer(env, array_ref, array); + gdk_texture_downloader_free(downloader); +} diff --git a/src/api-impl/android/graphics/Bitmap.java b/src/api-impl/android/graphics/Bitmap.java index f03d1aa2..a4b0edc9 100644 --- a/src/api-impl/android/graphics/Bitmap.java +++ b/src/api-impl/android/graphics/Bitmap.java @@ -1,5 +1,7 @@ package android.graphics; +import java.nio.Buffer; + import android.util.DisplayMetrics; /* @@ -9,28 +11,42 @@ import android.util.DisplayMetrics; public final class Bitmap { public enum Config { - RGB_565, - ARGB_8888, - ARGB_4444, - ALPHA_8, + RGB_565(2, -1, /*ANDROID_BITMAP_FORMAT_RGB_565*/4), + ARGB_8888(4, /*GDK_MEMORY_R8G8B8A8*/5, /**ANDROID_BITMAP_FORMAT_RGBA_8888*/1), + ARGB_4444(2, -1, /*ANDROID_BITMAP_FORMAT_RGBA_4444*/7), + ALPHA_8(1, /*GDK_MEMORY_A8*/ 24, /*ANDROID_BITMAP_FORMAT_A_8*/8); + + private int bytes_per_pixel; + private int gdk_memory_format; + int android_memory_format; // used by native function AndroidBitmap_getInfo() + + private Config(int bytes_per_pixel, int gdk_memory_format, int android_memory_format) { + this.bytes_per_pixel = bytes_per_pixel; + this.gdk_memory_format = gdk_memory_format; + this.android_memory_format = android_memory_format; + } } private int width; private int height; + private int stride; private long texture; private long snapshot; private Config config = Config.ARGB_8888; + private boolean hasAlpha = true; + long bytes = 0; // used by native function AndroidBitmap_lockPixels() Bitmap(long texture) { + this(native_get_width(texture), native_get_height(texture), Config.ARGB_8888); this.texture = texture; - this.width = native_get_width(texture); - this.height = native_get_height(texture); } private Bitmap(int width, int height, Config config) { this.config = config; this.width = width; this.height = height; + int stride = width * config.bytes_per_pixel; + this.stride = (stride + 3) & ~3; // 4-byte alignment } public static Bitmap createBitmap(int width, int height, Config config) { @@ -42,7 +58,9 @@ public final class Bitmap { } public static Bitmap createBitmap(DisplayMetrics metrics, int width, int height, Config config, boolean hasAlpha, ColorSpace colorSpace) { - return new Bitmap(width, height, config); + Bitmap bitmap = new Bitmap(width, height, config); + bitmap.hasAlpha = hasAlpha; + return bitmap; } public static Bitmap createBitmap(Bitmap src, int x, int y, int width, int height) { @@ -109,8 +127,12 @@ public final class Bitmap { snapshot = 0; } + public int getRowBytes() { + return stride; + } + public int getAllocationByteCount() { - return width * height * 4; + return height * getRowBytes(); } public void prepareToDraw() { @@ -131,9 +153,15 @@ public final class Bitmap { return texture == 0 && snapshot == 0; } - public void setHasAlpha(boolean hasAlpha) {} + public void setHasAlpha(boolean hasAlpha) { + this.hasAlpha = hasAlpha; + } - public Bitmap copy(Bitmap.Config config, boolean hasAlpha) { + public boolean hasAlpha() { + return hasAlpha; + } + + public Bitmap copy(Bitmap.Config config, boolean isMutable) { Bitmap bitmap = new Bitmap(width, height, config); bitmap.texture = native_ref_texture(getTexture()); return bitmap; @@ -143,6 +171,15 @@ public final class Bitmap { native_get_pixels(getTexture(), pixels, offset, stride, x, y, width, height); } + public void copyPixelsToBuffer(Buffer buffer) { + if (config.gdk_memory_format == -1) { + System.out.println("copyPixelsToBuffer: format " + config.name() + " not implemented"); + System.exit(1); + } + native_copy_to_buffer(getTexture(), buffer, config.gdk_memory_format, getRowBytes()); + buffer.position(buffer.position() + getAllocationByteCount()); + } + @SuppressWarnings("deprecation") @Override protected void finalize() throws Throwable { @@ -161,4 +198,5 @@ public final class Bitmap { private static native void native_recycle(long texture, long snapshot); private static native long native_ref_texture(long texture); private static native void native_get_pixels(long texture, int[] pixels, int offset, int stride, int x, int y, int width, int height); + private static native void native_copy_to_buffer(long texture, Buffer buffer, int memory_format, int stride); } diff --git a/src/libandroid/bitmap.c b/src/libandroid/bitmap.c index 5260d372..d3c1cd92 100644 --- a/src/libandroid/bitmap.c +++ b/src/libandroid/bitmap.c @@ -1,5 +1,6 @@ -#include +#include #include +#include // FIXME: put the header in a common place #include "../api-impl-jni/defines.h" @@ -14,19 +15,67 @@ struct AndroidBitmapInfo { uint32_t flags; }; -int AndroidBitmap_getInfo(JNIEnv* env, jobject bitmap, struct AndroidBitmapInfo *info) { - GdkPixbuf *pixbuf = _PTR(_GET_LONG_FIELD(bitmap, "pixbuf")); - info->width = gdk_pixbuf_get_width(pixbuf); - info->height = gdk_pixbuf_get_height(pixbuf); - info->stride = gdk_pixbuf_get_rowstride(pixbuf); - info->format = 1; // ANDROID_BITMAP_FORMAT_RGBA_8888 +int AndroidBitmap_getInfo(JNIEnv* env, jobject bitmap, struct AndroidBitmapInfo *info) +{ + info->width = _GET_INT_FIELD(bitmap, "width"); + info->height = _GET_INT_FIELD(bitmap, "height"); + info->stride = _GET_INT_FIELD(bitmap, "stride"); + info->format = _GET_INT_FIELD(_GET_OBJ_FIELD(bitmap, "config", "Landroid/graphics/Bitmap$Config;"), "android_memory_format"); return ANDROID_BITMAP_RESULT_SUCCESS; } -int AndroidBitmap_lockPixels(JNIEnv* env, jobject bitmap, void** pixels) { - GdkPixbuf *pixbuf = _PTR(_GET_LONG_FIELD(bitmap, "pixbuf")); - *pixels = gdk_pixbuf_get_pixels(pixbuf); + +int AndroidBitmap_lockPixels(JNIEnv* env, jobject bitmap, void** pixels) +{ + printf("AndroidBitmap_lockPixels\n"); + GdkTexture *texture = _PTR((*env)->CallLongMethod(env, bitmap, _METHOD(_CLASS(bitmap), "getTexture", "()J"))); + int stride = _GET_INT_FIELD(bitmap, "stride"); + int format = _GET_INT_FIELD(_GET_OBJ_FIELD(bitmap, "config", "Landroid/graphics/Bitmap$Config;"), "gdk_memory_format"); + if (format == -1) { + printf("AndroidBitmap_lockPixels: format not implemented\n"); + exit(1); + } + GdkTextureDownloader *downloader = gdk_texture_downloader_new(texture); + gdk_texture_downloader_set_format(downloader, format); + GBytes *bytes = NULL; + if (GDK_IS_MEMORY_TEXTURE(texture)) { // try to get the bytes non-copying + gsize texture_stride; + bytes = gdk_texture_downloader_download_bytes(downloader, &texture_stride); + if (texture_stride != stride) { // texture was not created by us, fall back to copy + g_bytes_unref(bytes); + bytes = NULL; + } + } + if (bytes == NULL) { + guchar *data = g_malloc(stride * gdk_texture_get_height(texture)); + gdk_texture_downloader_download_into(downloader, data, stride); + bytes = g_bytes_new_take(data, stride * gdk_texture_get_height(texture)); + } + gdk_texture_downloader_free(downloader); + _SET_LONG_FIELD(bitmap, "bytes", _INTPTR(bytes)); + *pixels = (void *)g_bytes_get_data(bytes, NULL); return ANDROID_BITMAP_RESULT_SUCCESS; } -int AndroidBitmap_unlockPixels(JNIEnv* env, jobject bitmap) { + +int AndroidBitmap_unlockPixels(JNIEnv* env, jobject bitmap) +{ + printf("AndroidBitmap_unlockPixels\n"); + GBytes *bytes = _PTR(_GET_LONG_FIELD(bitmap, "bytes")); + if (!bytes) { + printf("AndroidBitmap_unlockPixels: no bytes! Was AndroidBitmap_lockPixels called?\n"); + exit(1); + } + int width = _GET_INT_FIELD(bitmap, "width"); + int height = _GET_INT_FIELD(bitmap, "height"); + int stride = _GET_INT_FIELD(bitmap, "stride"); + int format = _GET_INT_FIELD(_GET_OBJ_FIELD(bitmap, "config", "Landroid/graphics/Bitmap$Config;"), "gdk_memory_format"); + if (format == -1) { + printf("AndroidBitmap_lockPixels: format not implemented\n"); + exit(1); + } + GdkTexture *texture = gdk_memory_texture_new(width, height, format, bytes, stride); + g_bytes_unref(bytes); + (*env)->CallVoidMethod(env, bitmap, _METHOD(_CLASS(bitmap), "recycle", "()V")); + _SET_LONG_FIELD(bitmap, "texture", _INTPTR(texture)); + _SET_LONG_FIELD(bitmap, "bytes", 0); return ANDROID_BITMAP_RESULT_SUCCESS; }