| /* |
| * Copyright 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #define LOG_TAG "ASurfaceControlTest" |
| |
| #include <ChoreographerTestUtils.h> |
| #include <android/choreographer.h> |
| #include <android/data_space.h> |
| #include <android/display_luts.h> |
| #include <android/hardware_buffer.h> |
| #include <android/hardware_buffer_jni.h> |
| #include <android/log.h> |
| #include <android/looper.h> |
| #include <android/native_window_jni.h> |
| #include <android/surface_control.h> |
| #include <android/surface_control_jni.h> |
| #include <android/sync.h> |
| #include <android/trace.h> |
| #include <errno.h> |
| #include <jni.h> |
| #include <nativehelper/ScopedPrimitiveArray.h> |
| #include <poll.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include <array> |
| #include <cinttypes> |
| #include <string> |
| |
| #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) |
| |
| namespace { |
| |
| struct { |
| jclass clazz; |
| jmethodID onTransactionComplete; |
| jmethodID shouldQueryTransactionStats; |
| jmethodID onTransactionStatsRead; |
| } gTransactionCompleteListenerClassInfo; |
| |
| struct { |
| jclass clazz; |
| jmethodID onBufferRelease; |
| } gBufferReleaseCallbackClassInfo; |
| |
| static AHardwareBuffer* allocateBuffer(int32_t width, int32_t height) { |
| AHardwareBuffer* buffer = nullptr; |
| AHardwareBuffer_Desc desc = {}; |
| desc.width = width; |
| desc.height = height; |
| desc.layers = 1; |
| desc.usage = AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | |
| AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE; |
| desc.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; |
| |
| AHardwareBuffer_allocate(&desc, &buffer); |
| |
| return buffer; |
| } |
| |
| static void fillRegion(void* data, int32_t left, int32_t top, int32_t right, |
| int32_t bottom, uint32_t color, uint32_t stride) { |
| uint32_t* ptr = static_cast<uint32_t*>(data); |
| |
| ptr += stride * top; |
| |
| // Convert color from incoming ARGB format to ABGR |
| uint32_t alpha = (color >> 24); |
| uint32_t red = (color >> 16) & 0xff; |
| uint32_t green = (color >> 8) & 0xff; |
| uint32_t blue = color & 0xff; |
| color = (alpha << 24) | (blue << 16) | (green << 8) | red; |
| |
| for (uint32_t y = top; y < bottom; y++) { |
| for (uint32_t x = left; x < right; x++) { |
| ptr[x] = color; |
| } |
| ptr += stride; |
| } |
| } |
| |
| static bool getSolidBuffer(int32_t width, int32_t height, uint32_t color, |
| AHardwareBuffer** outHardwareBuffer, |
| int* outFence) { |
| AHardwareBuffer* buffer = allocateBuffer(width, height); |
| if (!buffer) { |
| return true; |
| } |
| |
| AHardwareBuffer_Desc desc = {}; |
| AHardwareBuffer_describe(buffer, &desc); |
| |
| void* data = nullptr; |
| const ARect rect{0, 0, width, height}; |
| AHardwareBuffer_lock(buffer, AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, -1, &rect, |
| &data); |
| if (!data) { |
| AHardwareBuffer_release(buffer); |
| return true; |
| } |
| |
| fillRegion(data, 0, 0, width, height, color, desc.stride); |
| |
| AHardwareBuffer_unlock(buffer, outFence); |
| |
| *outHardwareBuffer = buffer; |
| return false; |
| } |
| |
| jobject Utils_getSolidBuffer(JNIEnv* env, jobject /*clazz*/, jint width, jint height, jint color) { |
| AHardwareBuffer* buffer; |
| if (getSolidBuffer(width, height, static_cast<uint32_t>(color), &buffer, nullptr)) { |
| return nullptr; |
| } |
| jobject result = AHardwareBuffer_toHardwareBuffer(env, buffer); |
| AHardwareBuffer_release(buffer); |
| return result; |
| } |
| |
| static bool getQuadrantBuffer(int32_t width, int32_t height, jint colorTopLeft, |
| jint colorTopRight, jint colorBottomRight, |
| jint colorBottomLeft, |
| AHardwareBuffer** outHardwareBuffer, |
| int* outFence) { |
| AHardwareBuffer* buffer = allocateBuffer(width, height); |
| if (!buffer) { |
| return true; |
| } |
| |
| AHardwareBuffer_Desc desc = {}; |
| AHardwareBuffer_describe(buffer, &desc); |
| |
| void* data = nullptr; |
| const ARect rect{0, 0, width, height}; |
| AHardwareBuffer_lock(buffer, AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, -1, &rect, |
| &data); |
| if (!data) { |
| return true; |
| } |
| |
| fillRegion(data, 0, 0, width / 2, height / 2, colorTopLeft, desc.stride); |
| fillRegion(data, width / 2, 0, width, height / 2, colorTopRight, desc.stride); |
| fillRegion(data, 0, height / 2, width / 2, height, colorBottomLeft, |
| desc.stride); |
| fillRegion(data, width / 2, height / 2, width, height, colorBottomRight, |
| desc.stride); |
| |
| AHardwareBuffer_unlock(buffer, outFence); |
| |
| *outHardwareBuffer = buffer; |
| return false; |
| } |
| |
| jobject Utils_getQuadrantBuffer(JNIEnv* env, jobject /*clazz*/, jint width, jint height, |
| jint colorTopLeft, jint colorTopRight, jint colorBottomRight, |
| jint colorBottomLeft) { |
| AHardwareBuffer* buffer; |
| if (getQuadrantBuffer(width, height, colorTopLeft, colorTopRight, colorBottomRight, |
| colorBottomLeft, &buffer, nullptr)) { |
| return nullptr; |
| } |
| jobject result = AHardwareBuffer_toHardwareBuffer(env, buffer); |
| AHardwareBuffer_release(buffer); |
| return result; |
| } |
| |
| jlong Utils_getBufferId(JNIEnv* env, jobject /*clazz*/, jobject jHardwareBuffer) { |
| AHardwareBuffer* buffer = AHardwareBuffer_fromHardwareBuffer(env, jHardwareBuffer); |
| uint64_t id = 0; |
| AHardwareBuffer_getId(buffer, &id); |
| return id; |
| } |
| |
| jlong SurfaceTransaction_create(JNIEnv* /*env*/, jclass) { |
| return reinterpret_cast<jlong>(ASurfaceTransaction_create()); |
| } |
| |
| void SurfaceTransaction_delete(JNIEnv* /*env*/, jclass, jlong surfaceTransaction) { |
| ASurfaceTransaction_delete( |
| reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction)); |
| } |
| |
| jlong SurfaceTransaction_fromJava(JNIEnv* env, jclass, jobject transactionObj) { |
| return reinterpret_cast<jlong>(ASurfaceTransaction_fromJava(env, transactionObj)); |
| } |
| |
| void SurfaceTransaction_apply(JNIEnv* /*env*/, jclass, jlong surfaceTransaction) { |
| ASurfaceTransaction_apply( |
| reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction)); |
| } |
| |
| long SurfaceControl_createFromWindow(JNIEnv* env, jclass, jobject jSurface) { |
| if (!jSurface) { |
| return 0; |
| } |
| |
| ANativeWindow* window = ANativeWindow_fromSurface(env, jSurface); |
| if (!window) { |
| return 0; |
| } |
| |
| const std::string debugName = "SurfaceControl_createFromWindowLayer"; |
| ASurfaceControl* surfaceControl = |
| ASurfaceControl_createFromWindow(window, debugName.c_str()); |
| if (!surfaceControl) { |
| return 0; |
| } |
| |
| ANativeWindow_release(window); |
| |
| return reinterpret_cast<jlong>(surfaceControl); |
| } |
| |
| jlong SurfaceControl_create(JNIEnv* /*env*/, jclass, jlong parentSurfaceControlId) { |
| ASurfaceControl* surfaceControl = nullptr; |
| const std::string debugName = "SurfaceControl_create"; |
| |
| surfaceControl = ASurfaceControl_create( |
| reinterpret_cast<ASurfaceControl*>(parentSurfaceControlId), |
| debugName.c_str()); |
| |
| return reinterpret_cast<jlong>(surfaceControl); |
| } |
| |
| void SurfaceControl_acquire(JNIEnv* /*env*/, jclass, jlong surfaceControl) { |
| ASurfaceControl_acquire(reinterpret_cast<ASurfaceControl*>(surfaceControl)); |
| } |
| |
| jlong SurfaceControl_fromJava(JNIEnv* env, jclass, jobject surfaceControlObj) { |
| return reinterpret_cast<jlong>(ASurfaceControl_fromJava(env, surfaceControlObj)); |
| } |
| |
| void SurfaceControl_release(JNIEnv* /*env*/, jclass, jlong surfaceControl) { |
| ASurfaceControl_release(reinterpret_cast<ASurfaceControl*>(surfaceControl)); |
| } |
| |
| class ReleaseCallbackWrapper { |
| public: |
| explicit ReleaseCallbackWrapper(JNIEnv* env, jobject jObject) { |
| env->GetJavaVM(&mVm); |
| mJavaCallbackObject = env->NewGlobalRef(jObject); |
| if (!mJavaCallbackObject) { |
| ALOGE("Failed to make ReleaseCallbackWrapper global ref"); |
| } |
| } |
| |
| ~ReleaseCallbackWrapper() { getenv()->DeleteGlobalRef(mJavaCallbackObject); } |
| |
| void callback() { |
| JNIEnv* env = getenv(); |
| env->CallVoidMethod(mJavaCallbackObject, gBufferReleaseCallbackClassInfo.onBufferRelease); |
| } |
| |
| static void callbackThunk(void* context, int /* release_fence_fd */) { |
| if (!context) { |
| ALOGE("Invalid context passed to callback"); |
| } |
| ReleaseCallbackWrapper* listener = reinterpret_cast<ReleaseCallbackWrapper*>(context); |
| listener->callback(); |
| delete listener; |
| } |
| |
| private: |
| jobject mJavaCallbackObject; |
| JavaVM* mVm; |
| |
| JNIEnv* getenv() { |
| JNIEnv* env; |
| int result = mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6); |
| if (result == JNI_EDETACHED) { |
| if (mVm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) { |
| ALOGE("Failed to AttachCurrentThread!"); |
| } |
| } else if (result != JNI_OK) { |
| ALOGE("Failed to get JNIEnv for JavaVM: %p", mVm); |
| } |
| |
| return env; |
| } |
| }; |
| |
| jlong SurfaceTransaction_setSolidBufferWithRelease(JNIEnv* env, jclass /* clazz */, |
| jlong surfaceControl, jlong surfaceTransaction, |
| jint width, jint height, jint color, |
| jobject jCallback) { |
| AHardwareBuffer* buffer = nullptr; |
| int fence = -1; |
| |
| bool err = getSolidBuffer(width, height, color, &buffer, &fence); |
| if (err) { |
| return 0; |
| } |
| |
| if (jCallback == nullptr) { |
| ALOGE("jCallback is null!"); |
| } |
| void* context = new ReleaseCallbackWrapper(env, jCallback); |
| ASurfaceTransaction_setBufferWithRelease(reinterpret_cast<ASurfaceTransaction*>( |
| surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), |
| buffer, fence, context, |
| ReleaseCallbackWrapper::callbackThunk); |
| |
| ASurfaceTransaction_setBufferDataSpace(reinterpret_cast<ASurfaceTransaction*>( |
| surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), |
| ADATASPACE_UNKNOWN); |
| |
| return reinterpret_cast<jlong>(buffer); |
| } |
| |
| jlong SurfaceTransaction_setSolidBuffer(JNIEnv* /*env*/, jclass, |
| jlong surfaceControl, |
| jlong surfaceTransaction, jint width, |
| jint height, jint color) { |
| AHardwareBuffer* buffer = nullptr; |
| int fence = -1; |
| |
| bool err = getSolidBuffer(width, height, color, &buffer, &fence); |
| if (err) { |
| return 0; |
| } |
| |
| ASurfaceTransaction_setBuffer( |
| reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), buffer, fence); |
| |
| ASurfaceTransaction_setBufferDataSpace( |
| reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), ADATASPACE_UNKNOWN); |
| |
| return reinterpret_cast<jlong>(buffer); |
| } |
| |
| void SurfaceTransaction_setBuffer(JNIEnv* /*env*/, jclass, jlong surfaceControl, |
| jlong surfaceTransaction, jlong buffer) { |
| ASurfaceTransaction_setBuffer(reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), |
| reinterpret_cast<AHardwareBuffer*>(buffer), -1 /* fence */); |
| |
| ASurfaceTransaction_setBufferDataSpace(reinterpret_cast<ASurfaceTransaction*>( |
| surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), |
| ADATASPACE_UNKNOWN); |
| } |
| |
| jlong SurfaceTransaction_setQuadrantBuffer( |
| JNIEnv* /*env*/, jclass, jlong surfaceControl, jlong surfaceTransaction, |
| jint width, jint height, jint colorTopLeft, jint colorTopRight, |
| jint colorBottomRight, jint colorBottomLeft) { |
| AHardwareBuffer* buffer = nullptr; |
| int fence = -1; |
| |
| bool err = |
| getQuadrantBuffer(width, height, colorTopLeft, colorTopRight, |
| colorBottomRight, colorBottomLeft, &buffer, &fence); |
| if (err) { |
| return 0; |
| } |
| |
| ASurfaceTransaction_setBuffer( |
| reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), buffer, fence); |
| |
| ASurfaceTransaction_setBufferDataSpace( |
| reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), ADATASPACE_UNKNOWN); |
| |
| return reinterpret_cast<jlong>(buffer); |
| } |
| |
| void SurfaceTransaction_releaseBuffer(JNIEnv* /*env*/, jclass, jlong buffer) { |
| AHardwareBuffer_release(reinterpret_cast<AHardwareBuffer*>(buffer)); |
| } |
| |
| void SurfaceTransaction_setVisibility(JNIEnv* /*env*/, jclass, |
| jlong surfaceControl, |
| jlong surfaceTransaction, jboolean show) { |
| auto visibility = (show) ? ASURFACE_TRANSACTION_VISIBILITY_SHOW : |
| ASURFACE_TRANSACTION_VISIBILITY_HIDE; |
| ASurfaceTransaction_setVisibility( |
| reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), visibility); |
| } |
| |
| void SurfaceTransaction_setBufferOpaque(JNIEnv* /*env*/, jclass, |
| jlong surfaceControl, |
| jlong surfaceTransaction, |
| jboolean opaque) { |
| auto transparency = (opaque) ? ASURFACE_TRANSACTION_TRANSPARENCY_OPAQUE : |
| ASURFACE_TRANSACTION_TRANSPARENCY_TRANSPARENT; |
| ASurfaceTransaction_setBufferTransparency( |
| reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), transparency); |
| } |
| |
| void SurfaceTransaction_setGeometry(JNIEnv* /*env*/, jclass, |
| jlong surfaceControl, |
| jlong surfaceTransaction, |
| jint srcLeft, jint srcTop, jint srcRight, jint srcBottom, |
| jint dstLeft, jint dstTop, jint dstRight, jint dstBottom, |
| jint transform) { |
| const ARect src{srcLeft, srcTop, srcRight, srcBottom}; |
| const ARect dst{dstLeft, dstTop, dstRight, dstBottom}; |
| ASurfaceTransaction_setGeometry( |
| reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), src, dst, transform); |
| } |
| |
| void SurfaceTransaction_setCrop(JNIEnv* /*env*/, jclass, jlong surfaceControl, |
| jlong surfaceTransaction, jint left, jint top, jint right, |
| jint bottom) { |
| const ARect crop{left, top, right, bottom}; |
| ASurfaceTransaction_setCrop(reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), crop); |
| } |
| |
| void SurfaceTransaction_setPosition(JNIEnv* /*env*/, jclass, jlong surfaceControl, |
| jlong surfaceTransaction, jint x, jint y) { |
| ASurfaceTransaction_setPosition(reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), x, y); |
| } |
| |
| void SurfaceTransaction_setBufferTransform(JNIEnv* /*env*/, jclass, jlong surfaceControl, |
| jlong surfaceTransaction, jint transform) { |
| ASurfaceTransaction_setBufferTransform(reinterpret_cast<ASurfaceTransaction*>( |
| surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), |
| transform); |
| } |
| |
| void SurfaceTransaction_setScale(JNIEnv* /*env*/, jclass, jlong surfaceControl, |
| jlong surfaceTransaction, jfloat xScale, jfloat yScale) { |
| ASurfaceTransaction_setScale(reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), xScale, |
| yScale); |
| } |
| |
| void SurfaceTransaction_setDamageRegion(JNIEnv* /*env*/, jclass, |
| jlong surfaceControl, |
| jlong surfaceTransaction, jint left, |
| jint top, jint right, jint bottom) { |
| const ARect rect[] = {{left, top, right, bottom}}; |
| ASurfaceTransaction_setDamageRegion( |
| reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), rect, 1); |
| } |
| |
| void SurfaceTransaction_setZOrder(JNIEnv* /*env*/, jclass, jlong surfaceControl, |
| jlong surfaceTransaction, jint z) { |
| ASurfaceTransaction_setZOrder( |
| reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), z); |
| } |
| |
| class CallbackListenerWrapper { |
| public: |
| explicit CallbackListenerWrapper(JNIEnv* env, jobject object, bool waitForFence) { |
| env->GetJavaVM(&mVm); |
| mCallbackListenerObject = env->NewGlobalRef(object); |
| mWaitForFence = waitForFence; |
| if (!mCallbackListenerObject) { |
| ALOGE("Failed to make global ref"); |
| } |
| } |
| |
| ~CallbackListenerWrapper() { getenv()->DeleteGlobalRef(mCallbackListenerObject); } |
| |
| /** |
| * This is duplicate code from sync.c, but the sync_wait function is not exposed to the ndk. |
| * The documentation recommends using poll instead of exposing sync_wait, but the sync_wait |
| * also handles errors and retries so copied the code here. |
| */ |
| static int sync_wait(int fd, int timeout) { |
| struct pollfd fds; |
| int ret; |
| |
| if (fd < 0) { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| fds.fd = fd; |
| fds.events = POLLIN; |
| |
| do { |
| ret = poll(&fds, 1, timeout); |
| if (ret > 0) { |
| if (fds.revents & (POLLERR | POLLNVAL)) { |
| errno = EINVAL; |
| return -1; |
| } |
| return 0; |
| } else if (ret == 0) { |
| errno = ETIME; |
| return -1; |
| } |
| } while (ret == -1 && (errno == EINTR || errno == EAGAIN)); |
| |
| return ret; |
| } |
| |
| static uint64_t getSignalTime(int presentFence) { |
| uint64_t presentTime = 0; |
| int error = sync_wait(presentFence, 500); |
| if (error < 0) { |
| ALOGE("Failed to sync fence error=%d", error); |
| return 0; |
| } |
| |
| struct sync_file_info* syncFileInfo = sync_file_info(presentFence); |
| if (!syncFileInfo) { |
| ALOGE("invalid fence"); |
| sync_file_info_free(syncFileInfo); |
| return 0; |
| } |
| |
| if (syncFileInfo->status != 1) { |
| ALOGE("fence did not signal status=%d", syncFileInfo->status); |
| return 0; |
| } |
| |
| struct sync_fence_info* syncFenceInfo = sync_get_fence_info(syncFileInfo); |
| for (size_t i = 0; i < syncFileInfo->num_fences; i++) { |
| if (syncFenceInfo[i].timestamp_ns > presentTime) { |
| presentTime = syncFenceInfo[i].timestamp_ns; |
| } |
| } |
| |
| sync_file_info_free(syncFileInfo); |
| close(presentFence); |
| return presentTime; |
| } |
| |
| void callback(ASurfaceTransactionStats* stats) { |
| JNIEnv* env = getenv(); |
| int64_t latchTime = ASurfaceTransactionStats_getLatchTime(stats); |
| uint64_t presentTime = systemTime(); |
| if (mWaitForFence) { |
| int presentFence = ASurfaceTransactionStats_getPresentFenceFd(stats); |
| if (presentFence >= 0) { |
| presentTime = getSignalTime(presentFence); |
| } |
| } |
| |
| jlong aSurfaceControlPtr = env->CallLongMethod(mCallbackListenerObject, |
| gTransactionCompleteListenerClassInfo |
| .shouldQueryTransactionStats); |
| ASurfaceControl* targetSurfaceControl = |
| reinterpret_cast<ASurfaceControl*>(aSurfaceControlPtr); |
| if (targetSurfaceControl) { |
| ASurfaceControl** surfaceControls = nullptr; |
| size_t surfaceControlsSize = 0; |
| ASurfaceTransactionStats_getASurfaceControls(stats, &surfaceControls, |
| &surfaceControlsSize); |
| bool surfaceControlFound = false; |
| bool releaseFenceQueried = false; |
| bool acquireTimeQueried = false; |
| for (int i = 0; i < surfaceControlsSize; i++) { |
| ASurfaceControl* surfaceControl = surfaceControls[i]; |
| if (targetSurfaceControl == surfaceControl) { |
| surfaceControlFound = true; |
| // Call the API, but ignore the result since the API is deprecated. |
| ASurfaceTransactionStats_getAcquireTime(stats, targetSurfaceControl); |
| acquireTimeQueried = true; |
| if (mWaitForFence) { |
| int previousReleaseFence = |
| ASurfaceTransactionStats_getPreviousReleaseFenceFd( |
| stats, targetSurfaceControl); |
| if (previousReleaseFence >= 0) { |
| getSignalTime(previousReleaseFence); |
| } |
| releaseFenceQueried = true; |
| } |
| } |
| } |
| ASurfaceTransactionStats_releaseASurfaceControls(surfaceControls); |
| env->CallVoidMethod(mCallbackListenerObject, |
| gTransactionCompleteListenerClassInfo.onTransactionStatsRead, |
| surfaceControlFound, releaseFenceQueried, acquireTimeQueried); |
| } |
| |
| env->CallVoidMethod(mCallbackListenerObject, |
| gTransactionCompleteListenerClassInfo.onTransactionComplete, latchTime, |
| presentTime); |
| } |
| |
| static void transactionCallbackThunk(void* context, ASurfaceTransactionStats* stats) { |
| CallbackListenerWrapper* listener = reinterpret_cast<CallbackListenerWrapper*>(context); |
| listener->callback(stats); |
| delete listener; |
| } |
| |
| private: |
| jobject mCallbackListenerObject; |
| JavaVM* mVm; |
| bool mWaitForFence; |
| |
| JNIEnv* getenv() { |
| JNIEnv* env; |
| mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6); |
| return env; |
| } |
| }; |
| |
| jlong SurfaceTransaction_setDesiredPresentTime(JNIEnv* /*env*/, jclass, jlong surfaceTransaction, |
| jlong desiredPresentTimeOffset) { |
| struct timespec t; |
| t.tv_sec = t.tv_nsec = 0; |
| clock_gettime(CLOCK_MONOTONIC, &t); |
| int64_t currentTime = ((int64_t) t.tv_sec)*1000000000LL + t.tv_nsec; |
| |
| int64_t desiredPresentTime = currentTime + desiredPresentTimeOffset; |
| |
| ASurfaceTransaction_setDesiredPresentTime( |
| reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), desiredPresentTime); |
| |
| return desiredPresentTime; |
| } |
| |
| void SurfaceTransaction_setBufferAlpha(JNIEnv* /*env*/, jclass, |
| jlong surfaceControl, |
| jlong surfaceTransaction, jdouble alpha) { |
| ASurfaceTransaction_setBufferAlpha( |
| reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), alpha); |
| } |
| |
| void SurfaceTransaction_reparent(JNIEnv* /*env*/, jclass, jlong surfaceControl, |
| jlong newParentSurfaceControl, jlong surfaceTransaction) { |
| ASurfaceTransaction_reparent( |
| reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), |
| reinterpret_cast<ASurfaceControl*>(newParentSurfaceControl)); |
| } |
| |
| void SurfaceTransaction_setColor(JNIEnv* /*env*/, jclass, jlong surfaceControl, |
| jlong surfaceTransaction, jfloat r, |
| jfloat g, jfloat b, jfloat alpha) { |
| ASurfaceTransaction_setColor( |
| reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), |
| r, g, b, alpha, ADATASPACE_UNKNOWN); |
| } |
| |
| void SurfaceTransaction_setEnableBackPressure(JNIEnv* env, jclass, jobject javaTransaction, |
| jobject javaSurfaceControl, |
| jboolean enableBackPressure) { |
| ASurfaceTransaction* transaction = ASurfaceTransaction_fromJava(env, javaTransaction); |
| ASurfaceControl* surfaceControl = ASurfaceControl_fromJava(env, javaSurfaceControl); |
| ASurfaceTransaction_setEnableBackPressure(transaction, surfaceControl, enableBackPressure); |
| } |
| |
| void SurfaceTransaction_setOnCompleteCallback(JNIEnv* env, jclass, jlong surfaceTransaction, |
| jboolean waitForFence, jobject callback) { |
| void* context = new CallbackListenerWrapper(env, callback, waitForFence); |
| ASurfaceTransaction_setOnComplete(reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| reinterpret_cast<void*>(context), |
| CallbackListenerWrapper::transactionCallbackThunk); |
| } |
| |
| void SurfaceTransaction_setOnCommitCallback(JNIEnv* env, jclass, jlong surfaceTransaction, |
| jobject callback) { |
| void* context = new CallbackListenerWrapper(env, callback, false /* waitForFence */); |
| ASurfaceTransaction_setOnCommit(reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| reinterpret_cast<void*>(context), |
| CallbackListenerWrapper::transactionCallbackThunk); |
| } |
| |
| // Save context so we can test callbacks without a context provided. |
| static CallbackListenerWrapper* listener = nullptr; |
| static void transactionCallbackWithoutContextThunk(void* /* context */, |
| ASurfaceTransactionStats* stats) { |
| CallbackListenerWrapper::transactionCallbackThunk(listener, stats); |
| listener = nullptr; |
| } |
| |
| void SurfaceTransaction_setOnCompleteCallbackWithoutContext(JNIEnv* env, jclass, |
| jlong surfaceTransaction, |
| jboolean waitForFence, |
| jobject callback) { |
| listener = new CallbackListenerWrapper(env, callback, waitForFence); |
| ASurfaceTransaction_setOnComplete(reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| nullptr, transactionCallbackWithoutContextThunk); |
| } |
| |
| void SurfaceTransaction_setOnCommitCallbackWithoutContext(JNIEnv* env, jclass, |
| jlong surfaceTransaction, |
| jobject callback) { |
| listener = new CallbackListenerWrapper(env, callback, false /* waitForFence */); |
| ASurfaceTransaction_setOnCommit(reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| nullptr, transactionCallbackWithoutContextThunk); |
| } |
| |
| void SurfaceTransaction_setFrameTimeline(JNIEnv* /*env*/, jclass, jlong surfaceTransaction, |
| jlong vsyncId) { |
| ASurfaceTransaction_setFrameTimeline(reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| vsyncId); |
| } |
| |
| void SurfaceTransaction_setExtendedRangeBrightness(JNIEnv* /*env*/, jclass, jlong surfaceControl, |
| jlong surfaceTransaction, jfloat currentRatio, |
| jfloat desiredRatio) { |
| ASurfaceTransaction_setExtendedRangeBrightness(reinterpret_cast<ASurfaceTransaction*>( |
| surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>( |
| surfaceControl), |
| currentRatio, desiredRatio); |
| } |
| |
| void SurfaceTransaction_setDesiredHdrHeadroom(JNIEnv* /*env*/, jclass, jlong surfaceControl, |
| jlong surfaceTransaction, jfloat desiredRatio) { |
| ASurfaceTransaction_setDesiredHdrHeadroom(reinterpret_cast<ASurfaceTransaction*>( |
| surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>( |
| surfaceControl), |
| desiredRatio); |
| } |
| |
| void SurfaceTransaction_setDataSpace(JNIEnv* /*env*/, jclass, jlong surfaceControl, |
| jlong surfaceTransaction, jint dataspace) { |
| ASurfaceTransaction_setBufferDataSpace(reinterpret_cast<ASurfaceTransaction*>( |
| surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), |
| static_cast<ADataSpace>(dataspace)); |
| } |
| |
| void SurfaceTransaction_setLuts(JNIEnv* env, jclass, jlong surfaceControl, jlong surfaceTransaction, |
| jfloatArray jbufferArray, jintArray joffsetArray, |
| jintArray jdimensionArray, jintArray jsizeArray, |
| jintArray jsamplingKeyArray) { |
| ADisplayLuts* luts = ADisplayLuts_create(); |
| if (jdimensionArray) { |
| jsize numLuts = env->GetArrayLength(jdimensionArray); |
| ScopedIntArrayRW joffsets(env, joffsetArray); |
| if (joffsets.get() == nullptr) { |
| jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from joffsetArray"); |
| return; |
| } |
| ScopedIntArrayRW jdimensions(env, jdimensionArray); |
| if (jdimensions.get() == nullptr) { |
| jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jdimensionArray"); |
| return; |
| } |
| ScopedIntArrayRW jsizes(env, jsizeArray); |
| if (jsizes.get() == nullptr) { |
| jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsizeArray"); |
| return; |
| } |
| ScopedIntArrayRW jsamplingKeys(env, jsamplingKeyArray); |
| if (jsamplingKeys.get() == nullptr) { |
| jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsamplingKeyArray"); |
| return; |
| } |
| |
| if (numLuts > 0) { |
| ScopedFloatArrayRW jbuffers(env, jbufferArray); |
| if (jbuffers.get() == nullptr) { |
| jniThrowRuntimeException(env, "Failed to get ScopedFloatArrayRW from jbufferArray"); |
| return; |
| } |
| int32_t bufferSize = (int32_t)env->GetArrayLength(jbufferArray); |
| std::vector<ADisplayLutsEntry*> entries; |
| entries.reserve(numLuts); |
| for (int32_t i = 0; i < numLuts; i++) { |
| int32_t bufferSizePerLut = (i + 1 == numLuts) ? bufferSize - joffsets[i] |
| : joffsets[i + 1] - joffsets[i]; |
| ADisplayLutsEntry* entry = |
| ADisplayLutsEntry_createEntry(jbuffers.get() + joffsets[i], bufferSizePerLut, |
| static_cast<ADisplayLuts_Dimension>(jdimensions[i]), |
| static_cast<ADisplayLuts_SamplingKey>(jsamplingKeys[i])); |
| entries.emplace_back(entry); |
| } |
| ADisplayLuts_setEntries(luts, entries.data(), numLuts); |
| for (int32_t i = 0; i < numLuts; i++) { |
| ADisplayLutsEntry_destroy(entries[i]); |
| } |
| } |
| } |
| |
| ASurfaceTransaction_setLuts(reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), |
| reinterpret_cast<ASurfaceControl*>(surfaceControl), luts); |
| ADisplayLuts_destroy(luts); |
| } |
| |
| static struct { |
| jclass clazz; |
| jmethodID constructor; |
| } gFrameTimelineClassInfo; |
| |
| static struct { |
| jclass clazz; |
| jmethodID constructor; |
| } gFrameCallbackDataClassInfo; |
| |
| static void verifyChoreographer(JNIEnv* env, AChoreographer* choreographer) { |
| ASSERT(choreographer != nullptr, "Choreographer setup unsuccessful"); |
| } |
| |
| static void verifyPollCallback(JNIEnv* env, int result) { |
| ASSERT(result == ALOOPER_POLL_CALLBACK, "Callback failed with error: %d", result); |
| } |
| |
| /** Gets VSync information from Choreographer, including a collection of frame timelines and |
| * platform-preferred index using Choreographer. */ |
| jobject SurfaceControlTest_getFrameTimelines(JNIEnv* env, jclass) { |
| ALooper_prepare(0); |
| ATrace_beginSection("Getting Choreographer instance"); |
| AChoreographer* choreographer = AChoreographer_getInstance(); |
| ATrace_endSection(); |
| verifyChoreographer(env, choreographer); |
| |
| VsyncCallback cb1("cb1", env); |
| auto start = now(); |
| ATrace_beginSection("postVsyncCallback"); |
| AChoreographer_postVsyncCallback(choreographer, vsyncCallback, &cb1); |
| ATrace_endSection(); |
| auto delayPeriod = std::chrono::duration_cast<std::chrono::milliseconds>(DELAY_PERIOD).count(); |
| ATrace_beginSection("ALooper_pollOnce"); |
| int result = ALooper_pollOnce(delayPeriod * 5, nullptr, nullptr, nullptr); |
| ATrace_endSection(); |
| verifyPollCallback(env, result); |
| verifyCallback(env, cb1, 1, start, NOMINAL_VSYNC_PERIOD * 3); |
| |
| jobjectArray frameTimelineObjs = |
| env->NewObjectArray(cb1.getTimeline().size(), gFrameTimelineClassInfo.clazz, |
| /*initial element*/ NULL); |
| if (env->ExceptionCheck()) { |
| env->ExceptionDescribe(); |
| env->ExceptionClear(); |
| return NULL; |
| } |
| if (frameTimelineObjs == NULL) { |
| jniThrowRuntimeException(env, "Failed to create FrameTimeline array"); |
| return NULL; |
| } |
| for (int i = 0; i < cb1.getTimeline().size(); i++) { |
| VsyncCallback::FrameTime frameTimeline = cb1.getTimeline()[i]; |
| jobject frameTimelineObj = |
| env->NewObject(gFrameTimelineClassInfo.clazz, gFrameTimelineClassInfo.constructor, |
| frameTimeline.vsyncId, frameTimeline.expectedPresentTime, |
| frameTimeline.deadline); |
| env->SetObjectArrayElement(frameTimelineObjs, i, frameTimelineObj); |
| } |
| |
| return env->NewObject(gFrameCallbackDataClassInfo.clazz, |
| gFrameCallbackDataClassInfo.constructor, frameTimelineObjs, |
| cb1.getPreferredFrameTimelineIndex()); |
| } |
| |
| static const JNINativeMethod JNI_METHODS[] = { |
| {"nSurfaceTransaction_create", "()J", (void*)SurfaceTransaction_create}, |
| {"nSurfaceTransaction_delete", "(J)V", (void*)SurfaceTransaction_delete}, |
| {"nSurfaceTransaction_fromJava", "(Landroid/view/SurfaceControl$Transaction;)J", |
| (void*)SurfaceTransaction_fromJava}, |
| {"nSurfaceTransaction_apply", "(J)V", (void*)SurfaceTransaction_apply}, |
| {"nSurfaceControl_createFromWindow", "(Landroid/view/Surface;)J", |
| (void*)SurfaceControl_createFromWindow}, |
| {"nSurfaceControl_create", "(J)J", (void*)SurfaceControl_create}, |
| {"nSurfaceControl_acquire", "(J)V", (void*)SurfaceControl_acquire}, |
| {"nSurfaceControl_release", "(J)V", (void*)SurfaceControl_release}, |
| {"nSurfaceControl_fromJava", "(Landroid/view/SurfaceControl;)J", |
| (void*)SurfaceControl_fromJava}, |
| {"nSurfaceTransaction_setSolidBuffer", "(JJIII)J", |
| (void*)SurfaceTransaction_setSolidBuffer}, |
| {"nSurfaceTransaction_setSolidBufferWithRelease", |
| "(JJIIILandroid/view/cts/util/ASurfaceControlTestUtils$BufferReleaseCallback;)J", |
| (void*)SurfaceTransaction_setSolidBufferWithRelease}, |
| {"nSurfaceTransaction_setBuffer", "(JJJ)V", (void*)SurfaceTransaction_setBuffer}, |
| {"nSurfaceTransaction_setQuadrantBuffer", "(JJIIIIII)J", |
| (void*)SurfaceTransaction_setQuadrantBuffer}, |
| {"nSurfaceTransaction_releaseBuffer", "(J)V", (void*)SurfaceTransaction_releaseBuffer}, |
| {"nSurfaceTransaction_setVisibility", "(JJZ)V", (void*)SurfaceTransaction_setVisibility}, |
| {"nSurfaceTransaction_setBufferOpaque", "(JJZ)V", |
| (void*)SurfaceTransaction_setBufferOpaque}, |
| {"nSurfaceTransaction_setGeometry", "(JJIIIIIIIII)V", |
| (void*)SurfaceTransaction_setGeometry}, |
| {"nSurfaceTransaction_setDamageRegion", "(JJIIII)V", |
| (void*)SurfaceTransaction_setDamageRegion}, |
| {"nSurfaceTransaction_setZOrder", "(JJI)V", (void*)SurfaceTransaction_setZOrder}, |
| {"nSurfaceTransaction_setDesiredPresentTime", "(JJ)J", |
| (void*)SurfaceTransaction_setDesiredPresentTime}, |
| {"nSurfaceTransaction_setBufferAlpha", "(JJD)V", (void*)SurfaceTransaction_setBufferAlpha}, |
| {"nSurfaceTransaction_reparent", "(JJJ)V", (void*)SurfaceTransaction_reparent}, |
| {"nSurfaceTransaction_setColor", "(JJFFFF)V", (void*)SurfaceTransaction_setColor}, |
| {"nSurfaceTransaction_setEnableBackPressure", |
| "(Landroid/view/SurfaceControl$Transaction;Landroid/view/SurfaceControl;Z)V", |
| (void*)SurfaceTransaction_setEnableBackPressure}, |
| {"nSurfaceTransaction_setOnCompleteCallback", |
| "(JZLandroid/view/cts/util/ASurfaceControlTestUtils$TransactionCompleteListener;)V", |
| (void*)SurfaceTransaction_setOnCompleteCallback}, |
| {"nSurfaceTransaction_setOnCommitCallback", |
| "(JLandroid/view/cts/util/ASurfaceControlTestUtils$TransactionCompleteListener;)V", |
| (void*)SurfaceTransaction_setOnCommitCallback}, |
| {"nSurfaceTransaction_setCrop", "(JJIIII)V", (void*)SurfaceTransaction_setCrop}, |
| {"nSurfaceTransaction_setPosition", "(JJII)V", (void*)SurfaceTransaction_setPosition}, |
| {"nSurfaceTransaction_setBufferTransform", "(JJI)V", |
| (void*)SurfaceTransaction_setBufferTransform}, |
| {"nSurfaceTransaction_setScale", "(JJFF)V", (void*)SurfaceTransaction_setScale}, |
| {"nSurfaceTransaction_setOnCompleteCallbackWithoutContext", |
| "(JZLandroid/view/cts/util/ASurfaceControlTestUtils$TransactionCompleteListener;)V", |
| (void*)SurfaceTransaction_setOnCompleteCallbackWithoutContext}, |
| {"nSurfaceTransaction_setOnCommitCallbackWithoutContext", |
| "(JLandroid/view/cts/util/ASurfaceControlTestUtils$TransactionCompleteListener;)V", |
| (void*)SurfaceTransaction_setOnCommitCallbackWithoutContext}, |
| {"nSurfaceTransaction_setFrameTimeline", "(JJ)V", |
| (void*)SurfaceTransaction_setFrameTimeline}, |
| {"nSurfaceTransaction_setExtendedRangeBrightness", "(JJFF)V", |
| (void*)SurfaceTransaction_setExtendedRangeBrightness}, |
| {"nSurfaceTransaction_setDesiredHdrHeadroom", "(JJF)V", |
| (void*)SurfaceTransaction_setDesiredHdrHeadroom}, |
| {"nSurfaceTransaction_setLuts", "(JJ[F[I[I[I[I)V", (void*)SurfaceTransaction_setLuts}, |
| {"nSurfaceTransaction_setDataSpace", "(JJI)V", (void*)SurfaceTransaction_setDataSpace}, |
| {"getSolidBuffer", "(III)Landroid/hardware/HardwareBuffer;", (void*)Utils_getSolidBuffer}, |
| {"getQuadrantBuffer", "(IIIIII)Landroid/hardware/HardwareBuffer;", |
| (void*)Utils_getQuadrantBuffer}, |
| {"getBufferId", "(Landroid/hardware/HardwareBuffer;)J", (void*)Utils_getBufferId}, |
| }; |
| |
| static const JNINativeMethod FRAME_TIMELINE_JNI_METHODS[] = { |
| {"nGetFrameTimelines", "()Landroid/view/cts/util/FrameCallbackData;", |
| (void*)SurfaceControlTest_getFrameTimelines}, |
| }; |
| |
| } // anonymous namespace |
| |
| jint register_android_view_cts_ASurfaceControlTest(JNIEnv* env) { |
| jclass transactionCompleteListenerClazz = env->FindClass( |
| "android/view/cts/util/ASurfaceControlTestUtils$TransactionCompleteListener"); |
| gTransactionCompleteListenerClassInfo.clazz = |
| static_cast<jclass>(env->NewGlobalRef(transactionCompleteListenerClazz)); |
| gTransactionCompleteListenerClassInfo.onTransactionComplete = |
| env->GetMethodID(transactionCompleteListenerClazz, "onTransactionComplete", "(JJ)V"); |
| gTransactionCompleteListenerClassInfo.shouldQueryTransactionStats = |
| env->GetMethodID(transactionCompleteListenerClazz, "shouldQueryTransactionStats", |
| "()J"); |
| gTransactionCompleteListenerClassInfo.onTransactionStatsRead = |
| env->GetMethodID(transactionCompleteListenerClazz, "onTransactionStatsRead", "(ZZZ)V"); |
| |
| jclass bufferReleaseCallbackClazz = |
| env->FindClass("android/view/cts/util/ASurfaceControlTestUtils$BufferReleaseCallback"); |
| gBufferReleaseCallbackClassInfo.clazz = |
| static_cast<jclass>(env->NewGlobalRef(bufferReleaseCallbackClazz)); |
| gBufferReleaseCallbackClassInfo.onBufferRelease = |
| env->GetMethodID(bufferReleaseCallbackClazz, "onBufferRelease", "()V"); |
| |
| gFrameTimelineClassInfo.clazz = static_cast<jclass>(env->NewGlobalRef( |
| env->FindClass("android/view/cts/util/FrameCallbackData$FrameTimeline"))); |
| gFrameTimelineClassInfo.constructor = |
| env->GetMethodID(gFrameTimelineClassInfo.clazz, "<init>", "(JJJ)V"); |
| |
| gFrameCallbackDataClassInfo.clazz = static_cast<jclass>( |
| env->NewGlobalRef(env->FindClass("android/view/cts/util/FrameCallbackData"))); |
| gFrameCallbackDataClassInfo.constructor = |
| env->GetMethodID(gFrameCallbackDataClassInfo.clazz, "<init>", |
| "([Landroid/view/cts/util/FrameCallbackData$FrameTimeline;I)V"); |
| |
| jclass frameCallbackDataClass = env->FindClass("android/view/cts/util/FrameCallbackData"); |
| env->RegisterNatives(frameCallbackDataClass, FRAME_TIMELINE_JNI_METHODS, |
| sizeof(FRAME_TIMELINE_JNI_METHODS) / sizeof(JNINativeMethod)); |
| |
| jclass clazz = env->FindClass("android/view/cts/util/ASurfaceControlTestUtils"); |
| return env->RegisterNatives(clazz, JNI_METHODS, sizeof(JNI_METHODS) / sizeof(JNINativeMethod)); |
| } |