From 98e452e39e76eff637cc9902580541af850ef78f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <99822064+revonateB0T@users.noreply.github.com> Date: Thu, 19 Sep 2024 22:25:55 +0800 Subject: [PATCH] Fastpath to create regioned hardware bitmap (#1647) * kotlin side * Impl * Refine * respect ahb align Signed-off-by: Dude so hot * Don't copy unnecessary things * Cleanup * Recycle * Revert "Disable `abortOnError` for lint in debug build" This reverts commit cea06f0c * Fix marshmallow build * Fix cmake * Address cr * Refine * Cleanup * log it --------- Signed-off-by: Dude so hot Co-authored-by: Dude so hot Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com> --- app/src/main/cpp/CMakeLists.txt | 3 ++ .../ehviewer/coil/CropBorderInterceptor.kt | 37 +++++++++++++++++-- .../coil/HardwareBitmapInterceptor.kt | 3 +- .../kotlin/com/hippo/ehviewer/image/Image.kt | 2 + app/src/main/rust/Cargo.toml | 3 ++ app/src/main/rust/src/img.rs | 1 + app/src/main/rust/src/img/copy_ahb.rs | 32 ++++++++++++++++ app/src/main/rust/src/lib.rs | 4 ++ 8 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 app/src/main/rust/src/img/copy_ahb.rs diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index dc715cb4f..9224d43f7 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -105,6 +105,9 @@ FetchContent_Declare( FetchContent_MakeAvailable(Corrosion) corrosion_import_crate(MANIFEST_PATH ../rust/Cargo.toml) corrosion_add_target_local_rustflags(ehviewer_rust "-Clink-args=-Wl,-z,max-page-size=16384") +if (ANDROID_NATIVE_API_LEVEL GREATER_EQUAL 26) + corrosion_set_features(ehviewer_rust FEATURES api-level-26) +endif () # Build and link our app's native lib add_library(${PROJECT_NAME} SHARED archive.c gifutils.c hash.c natsort/strnatcmp.c) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/coil/CropBorderInterceptor.kt b/app/src/main/kotlin/com/hippo/ehviewer/coil/CropBorderInterceptor.kt index 81fddbacf..bd117388c 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/coil/CropBorderInterceptor.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/coil/CropBorderInterceptor.kt @@ -1,12 +1,27 @@ package com.hippo.ehviewer.coil import android.graphics.Bitmap +import android.hardware.HardwareBuffer +import android.os.Build +import androidx.annotation.RequiresApi import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.toIntRect +import arrow.fx.coroutines.autoCloseable +import arrow.fx.coroutines.resourceScope import coil3.asImage import coil3.intercept.Interceptor import coil3.request.ImageResult import coil3.request.SuccessResult +import com.hippo.ehviewer.image.copyBitmapToAHB +import com.hippo.ehviewer.util.isAtLeastQ +import eu.kanade.tachiyomi.util.system.logcat +import moe.tarsin.coroutines.runSuspendCatching + +@RequiresApi(Build.VERSION_CODES.O) +private const val FORMAT = HardwareBuffer.RGBA_8888 + +@RequiresApi(Build.VERSION_CODES.O) +private const val USAGE = HardwareBuffer.USAGE_CPU_WRITE_RARELY or HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE object CropBorderInterceptor : Interceptor { override suspend fun intercept(chain: Interceptor.Chain): ImageResult { @@ -20,10 +35,24 @@ object CropBorderInterceptor : Interceptor { val (x, y) = image.rect.topLeft val (w, h) = image.rect.size val src = image.image.bitmap - Bitmap.createBitmap(src, x, y, w, h).also { cropped -> - src.recycle() - return result.copy(image = image.copy(image = cropped.asImage(), rect = image.rect.size.toIntRect())) - } + + // Large hardware bitmaps have rendering issues (e.g. crash, empty) on some devices. + // This is not ideal but I haven't figured out how to probe the threshold. + // All we know is that it's less than the maximum texture size. + val meetHardwareThreshold = maxOf(w, h) <= chain.request.hardwareThreshold + val bitmap = when { + isAtLeastQ && meetHardwareThreshold -> runSuspendCatching { + resourceScope { + val buffer = autoCloseable { HardwareBuffer.create(w, h, FORMAT, 1, USAGE) } + copyBitmapToAHB(src, buffer, x, y) + Bitmap.wrapHardwareBuffer(buffer, src.colorSpace) + } + }.onFailure { logcat(it) }.getOrNull() + else -> null + } ?: Bitmap.createBitmap(src, x, y, w, h) + + src.recycle() + return result.copy(image = image.copy(image = bitmap.asImage(), rect = image.rect.size.toIntRect())) } } } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/coil/HardwareBitmapInterceptor.kt b/app/src/main/kotlin/com/hippo/ehviewer/coil/HardwareBitmapInterceptor.kt index 9f78189a0..b0df7ea35 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/coil/HardwareBitmapInterceptor.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/coil/HardwareBitmapInterceptor.kt @@ -31,10 +31,11 @@ object HardwareBitmapInterceptor : Interceptor { val image = result.image if (image is BitmapImageWithExtraInfo) { val bitmap = image.image.bitmap + val isHardware = bitmap.config == Bitmap.Config.HARDWARE // Large hardware bitmaps have rendering issues (e.g. crash, empty) on some devices. // This is not ideal but I haven't figured out how to probe the threshold. // All we know is that it's less than the maximum texture size. - if (maxOf(bitmap.width, bitmap.height) <= request.hardwareThreshold) { + if (!isHardware && maxOf(bitmap.width, bitmap.height) <= request.hardwareThreshold) { bitmap.copy(Bitmap.Config.HARDWARE, false)?.let { hwBitmap -> bitmap.recycle() return result.copy(image = image.copy(image = hwBitmap.asImage())) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt b/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt index a6332f1ab..c39b6d5e5 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt @@ -18,6 +18,7 @@ package com.hippo.ehviewer.image import android.graphics.Bitmap +import android.hardware.HardwareBuffer import androidx.compose.ui.unit.IntSize import arrow.core.Either import arrow.core.left @@ -150,3 +151,4 @@ interface ByteBufferSource : ImageSource { external fun detectBorder(bitmap: Bitmap): IntArray external fun hasQrCode(bitmap: Bitmap): Boolean +external fun copyBitmapToAHB(src: Bitmap, dst: HardwareBuffer, x: Int, y: Int) diff --git a/app/src/main/rust/Cargo.toml b/app/src/main/rust/Cargo.toml index 7761f86e2..c5129c8a9 100644 --- a/app/src/main/rust/Cargo.toml +++ b/app/src/main/rust/Cargo.toml @@ -6,6 +6,9 @@ edition = "2021" [lib] crate-type = ["cdylib"] +[features] +api-level-26 = ["ndk/api-level-26"] + [dependencies] regex-lite = "0.1" jni = "0.21" diff --git a/app/src/main/rust/src/img.rs b/app/src/main/rust/src/img.rs index c7702487d..aca9e08ae 100644 --- a/app/src/main/rust/src/img.rs +++ b/app/src/main/rust/src/img.rs @@ -1,2 +1,3 @@ mod border; +mod copy_ahb; mod qr_code; diff --git a/app/src/main/rust/src/img/copy_ahb.rs b/app/src/main/rust/src/img/copy_ahb.rs new file mode 100644 index 000000000..93291c548 --- /dev/null +++ b/app/src/main/rust/src/img/copy_ahb.rs @@ -0,0 +1,32 @@ +#![cfg(feature = "api-level-26")] + +use crate::{jni_throwing, with_bitmap_content}; +use anyhow::anyhow; +use image::{GenericImage, GenericImageView, ImageBuffer}; +use jni::objects::JClass; +use jni::sys::{jint, jobject}; +use jni::JNIEnv; +use jni_fn::jni_fn; +use ndk::hardware_buffer::{HardwareBuffer, HardwareBufferUsage}; +use std::{ops::Deref, ptr::slice_from_raw_parts_mut}; + +#[no_mangle] +#[allow(non_snake_case)] +#[jni_fn("com.hippo.ehviewer.image.ImageKt")] +pub fn copyBitmapToAHB(mut env: JNIEnv, _: JClass, bm: jobject, ahb: jobject, x: jint, y: jint) { + let ahb = unsafe { HardwareBuffer::from_jni(env.get_raw(), ahb) }; + jni_throwing(&mut env, |env| { + with_bitmap_content(env, bm, |src| { + let desc = ahb.describe(); + let (w, h, stride) = (desc.width, desc.height, desc.stride); + let view = src.view(x as u32, y as u32, w, h); + let ptr = ahb.lock(HardwareBufferUsage::CPU_WRITE_RARELY, None, None)? as *mut u8; + let s = unsafe { &mut *slice_from_raw_parts_mut(ptr, (stride * h * 4) as usize) }; + let dst = ImageBuffer::from_raw(stride, h, s).ok_or(anyhow!("Illegal AHB format!")); + let result = dst.map(|mut dst| dst.copy_from(view.deref(), 0, 0)); + ahb.unlock()?; + result??; + Ok(()) + }) + }) +} diff --git a/app/src/main/rust/src/lib.rs b/app/src/main/rust/src/lib.rs index 4a093a962..fbffd0262 100644 --- a/app/src/main/rust/src/lib.rs +++ b/app/src/main/rust/src/lib.rs @@ -90,6 +90,10 @@ impl ThrowingHasDefault for i32 { } } +impl ThrowingHasDefault for () { + fn default() -> Self {} +} + fn jni_throwing(env: &mut JNIEnv, f: F) -> R where F: FnOnce(&mut JNIEnv) -> Result,