summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/binder/ActivityManager.cpp42
-rw-r--r--libs/binder/MemoryHeapBase.cpp4
-rw-r--r--libs/binder/Parcel.cpp52
-rw-r--r--libs/binder/tests/binderMemoryHeapBaseUnitTest.cpp13
-rw-r--r--libs/bufferqueueconverter/Android.bp2
-rw-r--r--libs/bufferstreams/Android.bp36
-rw-r--r--libs/bufferstreams/OWNERS7
-rw-r--r--libs/bufferstreams/README.md13
-rw-r--r--libs/bufferstreams/aconfig/bufferstreams_flags.aconfig65
-rw-r--r--libs/bufferstreams/examples/app/Android.bp49
-rw-r--r--libs/bufferstreams/examples/app/AndroidManifest.xml26
-rw-r--r--libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferDemosAppBar.kt40
-rw-r--r--libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt27
-rw-r--r--libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt35
-rw-r--r--libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen2.kt7
-rw-r--r--libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen3.kt7
-rw-r--r--libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/LogOutput.kt65
-rw-r--r--libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt132
-rw-r--r--libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Color.kt11
-rw-r--r--libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Theme.kt60
-rw-r--r--libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Type.kt18
-rw-r--r--libs/bufferstreams/examples/app/jni/Android.bp28
-rw-r--r--libs/bufferstreams/examples/app/jni/main.cpp53
-rw-r--r--libs/bufferstreams/examples/app/proguard-rules.pro23
-rw-r--r--libs/bufferstreams/examples/app/res/drawable/ic_launcher_background.xml170
-rw-r--r--libs/bufferstreams/examples/app/res/drawable/ic_launcher_foreground.xml30
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher.xml6
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher_round.xml6
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher.webpbin0 -> 1404 bytes
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher_round.webpbin0 -> 2898 bytes
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webpbin0 -> 982 bytes
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher_round.webpbin0 -> 1772 bytes
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webpbin0 -> 1900 bytes
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher_round.webpbin0 -> 3918 bytes
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webpbin0 -> 2884 bytes
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher_round.webpbin0 -> 5914 bytes
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webpbin0 -> 3844 bytes
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher_round.webpbin0 -> 7778 bytes
-rw-r--r--libs/bufferstreams/examples/app/res/values/colors.xml10
-rw-r--r--libs/bufferstreams/examples/app/res/values/strings.xml8
-rw-r--r--libs/bufferstreams/examples/app/res/values/themes.xml4
-rw-r--r--libs/bufferstreams/include/bufferstreams.h13
-rw-r--r--libs/bufferstreams/rust/Android.bp37
-rw-r--r--libs/bufferstreams/rust/Cargo.lock7
-rw-r--r--libs/bufferstreams/rust/Cargo.toml6
-rw-r--r--libs/bufferstreams/rust/cbindgen.toml149
-rw-r--r--libs/bufferstreams/rust/src/buffers/buffer.rs80
-rw-r--r--libs/bufferstreams/rust/src/buffers/buffer_owner.rs28
-rw-r--r--libs/bufferstreams/rust/src/buffers/buffer_pool.rs137
-rw-r--r--libs/bufferstreams/rust/src/buffers/mod.rs23
-rw-r--r--libs/bufferstreams/rust/src/lib.rs257
-rw-r--r--libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs112
-rw-r--r--libs/bufferstreams/rust/src/publishers/mod.rs20
-rw-r--r--libs/bufferstreams/rust/src/publishers/testing.rs103
-rw-r--r--libs/bufferstreams/rust/src/stream_config.rs67
-rw-r--r--libs/bufferstreams/rust/src/subscribers/mod.rs20
-rw-r--r--libs/bufferstreams/rust/src/subscribers/shared.rs94
-rw-r--r--libs/bufferstreams/rust/src/subscribers/testing.rs106
-rw-r--r--libs/bufferstreams/rust/src/subscriptions/mod.rs19
-rw-r--r--libs/bufferstreams/rust/src/subscriptions/shared_buffer_subscription.rs84
-rwxr-xr-xlibs/bufferstreams/update_include.sh2
-rw-r--r--libs/ftl/Android.bp1
-rw-r--r--libs/ftl/enum_test.cpp38
-rw-r--r--libs/ftl/function_test.cpp379
-rw-r--r--libs/gralloc/types/Android.bp2
-rw-r--r--libs/gralloc/types/Gralloc4.cpp4
-rw-r--r--libs/graphicsenv/IGpuService.cpp48
-rw-r--r--libs/graphicsenv/include/graphicsenv/IGpuService.h1
-rw-r--r--libs/gui/Android.bp77
-rw-r--r--libs/gui/BLASTBufferQueue.cpp41
-rw-r--r--libs/gui/BufferQueue.cpp10
-rw-r--r--libs/gui/BufferQueueProducer.cpp25
-rw-r--r--libs/gui/Choreographer.cpp8
-rw-r--r--libs/gui/DisplayEventDispatcher.cpp8
-rw-r--r--libs/gui/DisplayEventReceiver.cpp2
-rw-r--r--libs/gui/FrameRateUtils.cpp67
-rw-r--r--libs/gui/FrameTimestamps.cpp1
-rw-r--r--libs/gui/IGraphicBufferProducer.cpp40
-rw-r--r--libs/gui/ISurfaceComposer.cpp13
-rw-r--r--libs/gui/ITransactionCompletedListener.cpp16
-rw-r--r--libs/gui/LayerState.cpp52
-rw-r--r--libs/gui/Surface.cpp22
-rw-r--r--libs/gui/SurfaceComposerClient.cpp67
-rw-r--r--libs/gui/TEST_MAPPING3
-rw-r--r--libs/gui/WindowInfo.cpp81
-rw-r--r--libs/gui/aidl/android/gui/CaptureArgs.aidl (renamed from libs/renderengine/mock/Image.cpp)17
-rw-r--r--libs/gui/aidl/android/gui/DisplayMode.aidl4
-rw-r--r--libs/gui/aidl/android/gui/IDisplayEventConnection.aidl6
-rw-r--r--libs/gui/aidl/android/gui/ISurfaceComposer.aidl65
-rw-r--r--libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl3
-rw-r--r--libs/gui/aidl/android/gui/SchedulingPolicy.aidl (renamed from libs/renderengine/mock/Framebuffer.cpp)21
-rw-r--r--libs/gui/android/gui/TouchOcclusionMode.aidl3
-rw-r--r--libs/gui/fuzzer/Android.bp12
-rw-r--r--libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp4
-rw-r--r--libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp5
-rw-r--r--libs/gui/fuzzer/libgui_fuzzer_utils.h17
-rw-r--r--libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp21
-rw-r--r--libs/gui/include/gui/BLASTBufferQueue.h15
-rw-r--r--libs/gui/include/gui/BufferQueue.h9
-rw-r--r--libs/gui/include/gui/BufferQueueProducer.h6
-rw-r--r--libs/gui/include/gui/Choreographer.h3
-rw-r--r--libs/gui/include/gui/DisplayCaptureArgs.h1
-rw-r--r--libs/gui/include/gui/DisplayEventDispatcher.h3
-rw-r--r--libs/gui/include/gui/DisplayEventReceiver.h1
-rw-r--r--libs/gui/include/gui/FrameRateUtils.h (renamed from libs/renderengine/include/renderengine/Image.h)15
-rw-r--r--libs/gui/include/gui/IConsumerListener.h8
-rw-r--r--libs/gui/include/gui/IGraphicBufferProducer.h8
-rw-r--r--libs/gui/include/gui/ISurfaceComposer.h1
-rw-r--r--libs/gui/include/gui/ITransactionCompletedListener.h14
-rw-r--r--libs/gui/include/gui/JankInfo.h16
-rw-r--r--libs/gui/include/gui/LayerState.h31
-rw-r--r--libs/gui/include/gui/SchedulingPolicy.h40
-rw-r--r--libs/gui/include/gui/SurfaceComposerClient.h32
-rw-r--r--libs/gui/include/gui/WindowInfo.h12
-rw-r--r--libs/gui/libgui_flags.aconfig17
-rw-r--r--libs/gui/tests/Android.bp22
-rw-r--r--libs/gui/tests/BLASTBufferQueue_test.cpp275
-rw-r--r--libs/gui/tests/BufferQueue_test.cpp112
-rw-r--r--libs/gui/tests/DisplayEventStructLayout_test.cpp1
-rw-r--r--libs/gui/tests/EndToEndNativeInputTest.cpp28
-rw-r--r--libs/gui/tests/FrameRateUtilsTest.cpp74
-rw-r--r--libs/gui/tests/Surface_test.cpp102
-rw-r--r--libs/gui/tests/WindowInfo_test.cpp13
-rw-r--r--libs/input/Android.bp155
-rw-r--r--libs/input/Input.cpp26
-rw-r--r--libs/input/InputEventLabels.cpp24
-rw-r--r--libs/input/InputTransport.cpp141
-rw-r--r--libs/input/InputVerifier.cpp15
-rw-r--r--libs/input/KeyCharacterMap.cpp6
-rw-r--r--libs/input/MotionPredictor.cpp28
-rw-r--r--libs/input/MotionPredictorMetricsManager.cpp55
-rw-r--r--libs/input/VelocityControl.cpp2
-rw-r--r--libs/input/VelocityTracker.cpp574
-rw-r--r--libs/input/VirtualInputDevice.cpp1
-rw-r--r--libs/input/android/os/IInputConstants.aidl84
-rw-r--r--libs/input/input_flags.aconfig92
-rw-r--r--libs/input/input_verifier.rs419
-rw-r--r--libs/input/rust/Android.bp72
-rw-r--r--libs/input/rust/input.rs217
-rw-r--r--libs/input/rust/input_verifier.rs638
-rw-r--r--libs/input/rust/lib.rs97
-rw-r--r--libs/input/tests/Android.bp1
-rw-r--r--libs/input/tests/InputDevice_test.cpp26
-rw-r--r--libs/input/tests/InputPublisherAndConsumer_test.cpp187
-rw-r--r--libs/input/tests/InputVerifier_test.cpp25
-rw-r--r--libs/input/tests/MotionPredictorMetricsManager_test.cpp79
-rw-r--r--libs/input/tests/MotionPredictor_test.cpp31
-rw-r--r--libs/input/tests/TouchResampling_test.cpp53
-rw-r--r--libs/input/tests/VelocityTracker_test.cpp203
-rw-r--r--libs/nativedisplay/ADisplay.cpp2
-rw-r--r--libs/nativedisplay/Android.bp2
-rw-r--r--libs/nativedisplay/include/surfacetexture/SurfaceTexture.h45
-rw-r--r--libs/nativedisplay/surfacetexture/SurfaceTexture.cpp40
-rw-r--r--libs/nativewindow/AHardwareBuffer.cpp225
-rw-r--r--libs/nativewindow/TEST_MAPPING8
-rw-r--r--libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h6
-rw-r--r--libs/nativewindow/include/android/native_window_aidl.h26
-rw-r--r--libs/nativewindow/include/system/window.h80
-rw-r--r--libs/nativewindow/libnativewindow.map.txt1
-rw-r--r--libs/permission/aidl/android/content/AttributionSourceState.aidl4
-rw-r--r--libs/renderengine/Android.bp26
-rw-r--r--libs/renderengine/Description.cpp61
-rw-r--r--libs/renderengine/ExternalTexture.cpp8
-rw-r--r--libs/renderengine/Mesh.cpp146
-rw-r--r--libs/renderengine/RenderEngine.cpp14
-rw-r--r--libs/renderengine/Texture.cpp77
-rw-r--r--libs/renderengine/benchmark/RenderEngineBench.cpp20
-rw-r--r--libs/renderengine/gl/GLESRenderEngine.cpp1866
-rw-r--r--libs/renderengine/gl/GLESRenderEngine.h313
-rw-r--r--libs/renderengine/gl/GLFramebuffer.cpp115
-rw-r--r--libs/renderengine/gl/GLFramebuffer.h68
-rw-r--r--libs/renderengine/gl/GLImage.cpp83
-rw-r--r--libs/renderengine/gl/GLImage.h54
-rw-r--r--libs/renderengine/gl/GLShadowTexture.cpp52
-rw-r--r--libs/renderengine/gl/GLShadowTexture.h44
-rw-r--r--libs/renderengine/gl/GLShadowVertexGenerator.cpp98
-rw-r--r--libs/renderengine/gl/GLShadowVertexGenerator.h65
-rw-r--r--libs/renderengine/gl/GLSkiaShadowPort.cpp656
-rw-r--r--libs/renderengine/gl/GLSkiaShadowPort.h96
-rw-r--r--libs/renderengine/gl/GLVertexBuffer.cpp55
-rw-r--r--libs/renderengine/gl/GLVertexBuffer.h49
-rw-r--r--libs/renderengine/gl/ImageManager.cpp148
-rw-r--r--libs/renderengine/gl/ImageManager.h74
-rw-r--r--libs/renderengine/gl/Program.cpp175
-rw-r--r--libs/renderengine/gl/Program.h120
-rw-r--r--libs/renderengine/gl/ProgramCache.cpp830
-rw-r--r--libs/renderengine/gl/ProgramCache.h239
-rw-r--r--libs/renderengine/gl/filters/BlurFilter.cpp268
-rw-r--r--libs/renderengine/gl/filters/BlurFilter.h95
-rw-r--r--libs/renderengine/gl/filters/GenericProgram.cpp122
-rw-r--r--libs/renderengine/gl/filters/GenericProgram.h51
-rw-r--r--libs/renderengine/include/renderengine/Framebuffer.h35
-rw-r--r--libs/renderengine/include/renderengine/LayerSettings.h55
-rw-r--r--libs/renderengine/include/renderengine/Mesh.h205
-rw-r--r--libs/renderengine/include/renderengine/RenderEngine.h34
-rw-r--r--libs/renderengine/include/renderengine/Texture.h60
-rw-r--r--libs/renderengine/include/renderengine/mock/Framebuffer.h36
-rw-r--r--libs/renderengine/include/renderengine/mock/Image.h36
-rw-r--r--libs/renderengine/include/renderengine/mock/RenderEngine.h16
-rw-r--r--libs/renderengine/include/renderengine/private/Description.h95
-rw-r--r--libs/renderengine/skia/AutoBackendTexture.cpp71
-rw-r--r--libs/renderengine/skia/AutoBackendTexture.h2
-rw-r--r--libs/renderengine/skia/Cache.cpp398
-rw-r--r--libs/renderengine/skia/Cache.h2
-rw-r--r--libs/renderengine/skia/GLExtensions.cpp (renamed from libs/renderengine/gl/GLExtensions.cpp)6
-rw-r--r--libs/renderengine/skia/GLExtensions.h (renamed from libs/renderengine/gl/GLExtensions.h)4
-rw-r--r--libs/renderengine/skia/SkiaGLRenderEngine.cpp45
-rw-r--r--libs/renderengine/skia/SkiaRenderEngine.cpp45
-rw-r--r--libs/renderengine/skia/SkiaRenderEngine.h12
-rw-r--r--libs/renderengine/skia/SkiaVkRenderEngine.cpp15
-rw-r--r--libs/renderengine/skia/debug/SkiaCapture.cpp14
-rw-r--r--libs/renderengine/skia/debug/SkiaMemoryReporter.cpp15
-rw-r--r--libs/renderengine/skia/debug/SkiaMemoryReporter.h5
-rwxr-xr-xlibs/renderengine/skia/debug/record.sh2
-rw-r--r--libs/renderengine/skia/filters/BlurFilter.cpp2
-rw-r--r--libs/renderengine/skia/filters/GaussianBlurFilter.cpp7
-rw-r--r--libs/renderengine/skia/filters/KawaseBlurFilter.cpp95
-rw-r--r--libs/renderengine/tests/RenderEngineTest.cpp133
-rw-r--r--libs/renderengine/tests/RenderEngineThreadedTest.cpp50
-rw-r--r--libs/renderengine/threaded/RenderEngineThreaded.cpp110
-rw-r--r--libs/renderengine/threaded/RenderEngineThreaded.h9
-rw-r--r--libs/sensor/Android.bp1
-rw-r--r--libs/sensor/ISensorServer.cpp52
-rw-r--r--libs/sensor/SensorManager.cpp74
-rw-r--r--libs/sensor/include/sensor/ISensorServer.h2
-rw-r--r--libs/sensor/include/sensor/SensorManager.h5
-rw-r--r--libs/sensorprivacy/SensorPrivacyManager.cpp23
-rw-r--r--libs/ui/FenceTime.cpp8
-rw-r--r--libs/ui/Gralloc5.cpp225
-rw-r--r--libs/ui/GraphicBufferAllocator.cpp4
-rw-r--r--libs/ui/include/ui/DisplayMap.h11
-rw-r--r--libs/ui/include/ui/DisplayMode.h4
-rw-r--r--libs/ui/include/ui/GraphicBuffer.h17
-rw-r--r--libs/ui/include/ui/ShadowSettings.h65
-rw-r--r--libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp2
-rw-r--r--libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp134
-rw-r--r--libs/ultrahdr/gainmapmath.cpp68
-rw-r--r--libs/ultrahdr/icc.cpp45
-rw-r--r--libs/ultrahdr/include/ultrahdr/gainmapmath.h27
-rw-r--r--libs/ultrahdr/include/ultrahdr/icc.h3
-rw-r--r--libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h24
-rw-r--r--libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h21
-rw-r--r--libs/ultrahdr/include/ultrahdr/jpegr.h273
-rw-r--r--libs/ultrahdr/include/ultrahdr/ultrahdr.h3
-rw-r--r--libs/ultrahdr/jpegdecoderhelper.cpp212
-rw-r--r--libs/ultrahdr/jpegencoderhelper.cpp137
-rw-r--r--libs/ultrahdr/jpegr.cpp1268
-rw-r--r--libs/ultrahdr/tests/AndroidTest.xml26
-rw-r--r--libs/ultrahdr/tests/data/raw_p010_image_with_stride.p010bin2782080 -> 0 bytes
-rw-r--r--libs/ultrahdr/tests/gainmapmath_test.cpp7
-rw-r--r--libs/ultrahdr/tests/jpegdecoderhelper_test.cpp20
-rw-r--r--libs/ultrahdr/tests/jpegencoderhelper_test.cpp31
-rw-r--r--libs/ultrahdr/tests/jpegr_test.cpp2972
253 files changed, 10548 insertions, 11064 deletions
diff --git a/libs/binder/ActivityManager.cpp b/libs/binder/ActivityManager.cpp
index aca5009148..526427663b 100644
--- a/libs/binder/ActivityManager.cpp
+++ b/libs/binder/ActivityManager.cpp
@@ -21,6 +21,7 @@
#include <binder/ActivityManager.h>
#include <binder/Binder.h>
#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
#include <utils/SystemClock.h>
@@ -33,27 +34,36 @@ ActivityManager::ActivityManager()
sp<IActivityManager> ActivityManager::getService()
{
std::lock_guard<Mutex> scoped_lock(mLock);
- int64_t startTime = 0;
sp<IActivityManager> service = mService;
- while (service == nullptr || !IInterface::asBinder(service)->isBinderAlive()) {
- sp<IBinder> binder = defaultServiceManager()->checkService(String16("activity"));
- if (binder == nullptr) {
- // Wait for the activity service to come back...
- if (startTime == 0) {
- startTime = uptimeMillis();
- ALOGI("Waiting for activity service");
- } else if ((uptimeMillis() - startTime) > 1000000) {
- ALOGW("Waiting too long for activity service, giving up");
- service = nullptr;
- break;
- }
- usleep(25000);
- } else {
+ if (ProcessState::self()->isThreadPoolStarted()) {
+ if (service == nullptr || !IInterface::asBinder(service)->isBinderAlive()) {
+ sp<IBinder> binder = defaultServiceManager()->waitForService(String16("activity"));
service = interface_cast<IActivityManager>(binder);
mService = service;
}
+ } else {
+ ALOGI("Thread pool not started. Polling for activity service.");
+ int64_t startTime = 0;
+ while (service == nullptr || !IInterface::asBinder(service)->isBinderAlive()) {
+ sp<IBinder> binder = defaultServiceManager()->checkService(String16("activity"));
+ if (binder == nullptr) {
+ // Wait for the activity service to come back...
+ if (startTime == 0) {
+ startTime = uptimeMillis();
+ ALOGI("Waiting for activity service");
+ } else if ((uptimeMillis() - startTime) > 1000000) {
+ ALOGW("Waiting too long for activity service, giving up");
+ service = nullptr;
+ break;
+ }
+ usleep(25000);
+ } else {
+ service = interface_cast<IActivityManager>(binder);
+ mService = service;
+ }
+ }
}
- return service;
+ return mService;
}
int ActivityManager::openContentUri(const String16& stringUri)
diff --git a/libs/binder/MemoryHeapBase.cpp b/libs/binder/MemoryHeapBase.cpp
index 3da06ba4db..fc273e0367 100644
--- a/libs/binder/MemoryHeapBase.cpp
+++ b/libs/binder/MemoryHeapBase.cpp
@@ -73,8 +73,8 @@ MemoryHeapBase::MemoryHeapBase(size_t size, uint32_t flags, char const * name)
ALOGV("MemoryHeapBase: Attempting to force MemFD");
fd = memfd_create_region(name ? name : "MemoryHeapBase", size);
if (fd < 0 || (mapfd(fd, true, size) != NO_ERROR)) return;
- const int SEAL_FLAGS = ((mFlags & READ_ONLY) ? F_SEAL_FUTURE_WRITE : 0) |
- ((mFlags & MEMFD_ALLOW_SEALING_FLAG) ? 0 : F_SEAL_SEAL);
+ const int SEAL_FLAGS = ((mFlags & READ_ONLY) ? F_SEAL_FUTURE_WRITE : 0) | F_SEAL_GROW |
+ F_SEAL_SHRINK | ((mFlags & MEMFD_ALLOW_SEALING_FLAG) ? 0 : F_SEAL_SEAL);
if (SEAL_FLAGS && (fcntl(fd, F_ADD_SEALS, SEAL_FLAGS) == -1)) {
ALOGE("MemoryHeapBase: MemFD %s sealing with flags %x failed with error %s", name,
SEAL_FLAGS, strerror(errno));
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 7a54a03389..c1770b35d1 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -68,6 +68,10 @@
typedef uintptr_t binder_uintptr_t;
#endif // BINDER_WITH_KERNEL_IPC
+#ifdef __BIONIC__
+#include <android/fdsan.h>
+#endif
+
#define LOG_REFS(...)
// #define LOG_REFS(...) ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOG_ALLOC(...)
@@ -112,6 +116,37 @@ constexpr size_t kMaxFds = 1024;
// Maximum size of a blob to transfer in-place.
[[maybe_unused]] static const size_t BLOB_INPLACE_LIMIT = 16 * 1024;
+#if defined(__BIONIC__)
+static void FdTag(int fd, const void* old_addr, const void* new_addr) {
+ if (android_fdsan_exchange_owner_tag) {
+ uint64_t old_tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_PARCEL,
+ reinterpret_cast<uint64_t>(old_addr));
+ uint64_t new_tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_PARCEL,
+ reinterpret_cast<uint64_t>(new_addr));
+ android_fdsan_exchange_owner_tag(fd, old_tag, new_tag);
+ }
+}
+static void FdTagClose(int fd, const void* addr) {
+ if (android_fdsan_close_with_tag) {
+ uint64_t tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_PARCEL,
+ reinterpret_cast<uint64_t>(addr));
+ android_fdsan_close_with_tag(fd, tag);
+ } else {
+ close(fd);
+ }
+}
+#else
+static void FdTag(int fd, const void* old_addr, const void* new_addr) {
+ (void)fd;
+ (void)old_addr;
+ (void)new_addr;
+}
+static void FdTagClose(int fd, const void* addr) {
+ (void)addr;
+ close(fd);
+}
+#endif
+
enum {
BLOB_INPLACE = 0,
BLOB_ASHMEM_IMMUTABLE = 1,
@@ -137,6 +172,9 @@ static void acquire_object(const sp<ProcessState>& proc, const flat_binder_objec
return;
}
case BINDER_TYPE_FD: {
+ if (obj.cookie != 0) { // owned
+ FdTag(obj.handle, nullptr, who);
+ }
return;
}
}
@@ -162,8 +200,10 @@ static void release_object(const sp<ProcessState>& proc, const flat_binder_objec
return;
}
case BINDER_TYPE_FD: {
+ // note: this path is not used when mOwner, so the tag is also released
+ // in 'closeFileDescriptors'
if (obj.cookie != 0) { // owned
- close(obj.handle);
+ FdTagClose(obj.handle, who);
}
return;
}
@@ -557,7 +597,6 @@ status_t Parcel::appendFrom(const Parcel* parcel, size_t offset, size_t len) {
kernelFields->mObjectsSize++;
flat_binder_object* flat = reinterpret_cast<flat_binder_object*>(mData + off);
- acquire_object(proc, *flat, this);
if (flat->hdr.type == BINDER_TYPE_FD) {
// If this is a file descriptor, we need to dup it so the
@@ -570,6 +609,8 @@ status_t Parcel::appendFrom(const Parcel* parcel, size_t offset, size_t len) {
err = FDS_NOT_ALLOWED;
}
}
+
+ acquire_object(proc, *flat, this);
}
}
#else
@@ -2596,7 +2637,8 @@ void Parcel::closeFileDescriptors() {
reinterpret_cast<flat_binder_object*>(mData + kernelFields->mObjects[i]);
if (flat->hdr.type == BINDER_TYPE_FD) {
// ALOGI("Closing fd: %ld", flat->handle);
- close(flat->handle);
+ // FDs from the kernel are always owned
+ FdTagClose(flat->handle, this);
}
}
#else // BINDER_WITH_KERNEL_IPC
@@ -2677,6 +2719,10 @@ void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize, const bin
kernelFields->mObjectsSize = 0;
break;
}
+ if (type == BINDER_TYPE_FD) {
+ // FDs from the kernel are always owned
+ FdTag(flat->handle, 0, this);
+ }
minOffset = offset + sizeof(flat_binder_object);
}
scanForFds();
diff --git a/libs/binder/tests/binderMemoryHeapBaseUnitTest.cpp b/libs/binder/tests/binderMemoryHeapBaseUnitTest.cpp
index 278dd2bf81..140270f5a1 100644
--- a/libs/binder/tests/binderMemoryHeapBaseUnitTest.cpp
+++ b/libs/binder/tests/binderMemoryHeapBaseUnitTest.cpp
@@ -37,7 +37,8 @@ TEST(MemoryHeapBase, MemfdSealed) {
ASSERT_NE(mHeap.get(), nullptr);
int fd = mHeap->getHeapID();
EXPECT_NE(fd, -1);
- EXPECT_EQ(fcntl(fd, F_GET_SEALS), F_SEAL_SEAL);
+ EXPECT_EQ(fcntl(fd, F_GET_SEALS), F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL);
+ EXPECT_EQ(ftruncate(fd, 4096), -1);
}
TEST(MemoryHeapBase, MemfdUnsealed) {
@@ -48,7 +49,8 @@ TEST(MemoryHeapBase, MemfdUnsealed) {
ASSERT_NE(mHeap.get(), nullptr);
int fd = mHeap->getHeapID();
EXPECT_NE(fd, -1);
- EXPECT_EQ(fcntl(fd, F_GET_SEALS), 0);
+ EXPECT_EQ(fcntl(fd, F_GET_SEALS), F_SEAL_GROW | F_SEAL_SHRINK);
+ EXPECT_EQ(ftruncate(fd, 4096), -1);
}
TEST(MemoryHeapBase, MemfdSealedProtected) {
@@ -59,7 +61,9 @@ TEST(MemoryHeapBase, MemfdSealedProtected) {
ASSERT_NE(mHeap.get(), nullptr);
int fd = mHeap->getHeapID();
EXPECT_NE(fd, -1);
- EXPECT_EQ(fcntl(fd, F_GET_SEALS), F_SEAL_SEAL | F_SEAL_FUTURE_WRITE);
+ EXPECT_EQ(fcntl(fd, F_GET_SEALS),
+ F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL | F_SEAL_FUTURE_WRITE);
+ EXPECT_EQ(ftruncate(fd, 4096), -1);
}
TEST(MemoryHeapBase, MemfdUnsealedProtected) {
@@ -71,7 +75,8 @@ TEST(MemoryHeapBase, MemfdUnsealedProtected) {
ASSERT_NE(mHeap.get(), nullptr);
int fd = mHeap->getHeapID();
EXPECT_NE(fd, -1);
- EXPECT_EQ(fcntl(fd, F_GET_SEALS), F_SEAL_FUTURE_WRITE);
+ EXPECT_EQ(fcntl(fd, F_GET_SEALS), F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_FUTURE_WRITE);
+ EXPECT_EQ(ftruncate(fd, 4096), -1);
}
#else
diff --git a/libs/bufferqueueconverter/Android.bp b/libs/bufferqueueconverter/Android.bp
index d4605ea13a..3fe71cefce 100644
--- a/libs/bufferqueueconverter/Android.bp
+++ b/libs/bufferqueueconverter/Android.bp
@@ -34,5 +34,7 @@ cc_library {
"libbase",
"liblog",
],
+ static_libs: ["libguiflags"],
export_include_dirs: ["include"],
+ export_static_lib_headers: ["libguiflags"],
}
diff --git a/libs/bufferstreams/Android.bp b/libs/bufferstreams/Android.bp
new file mode 100644
index 0000000000..365fc457d1
--- /dev/null
+++ b/libs/bufferstreams/Android.bp
@@ -0,0 +1,36 @@
+// Copyright (C) 2023 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.
+
+package {
+ default_applicable_licenses: ["frameworks_native_license"],
+}
+
+aconfig_declarations {
+ name: "bufferstreams_flags",
+ package: "com.android.graphics.bufferstreams.flags",
+ srcs: [
+ "aconfig/bufferstreams_flags.aconfig",
+ ],
+}
+
+rust_aconfig_library {
+ name: "libbufferstreams_flags_rust",
+ crate_name: "bufferstreams_flags",
+ aconfig_declarations: "bufferstreams_flags",
+}
+
+cc_aconfig_library {
+ name: "libbufferstreams_flags_cc",
+ aconfig_declarations: "bufferstreams_flags",
+}
diff --git a/libs/bufferstreams/OWNERS b/libs/bufferstreams/OWNERS
new file mode 100644
index 0000000000..32b72b8592
--- /dev/null
+++ b/libs/bufferstreams/OWNERS
@@ -0,0 +1,7 @@
+carlosmr@google.com
+hibrian@google.com
+jreck@google.com
+jshargo@google.com
+
+file:/services/surfaceflinger/OWNERS
+
diff --git a/libs/bufferstreams/README.md b/libs/bufferstreams/README.md
new file mode 100644
index 0000000000..860adef281
--- /dev/null
+++ b/libs/bufferstreams/README.md
@@ -0,0 +1,13 @@
+# libbufferstreams: Reactive Streams for Graphics Buffers
+
+This library is currently **experimental** and **under active development**.
+It is not production ready yet.
+
+For more information on reactive streams, please see <https://www.reactive-streams.org/>
+
+## Contributing
+
+This library is natively written in Rust and exposes a C API. If you make changes to the Rust API,
+you **must** update the C API in turn. To do so, with cbindgen installed, run:
+
+```$ ./update_include.sh```
diff --git a/libs/bufferstreams/aconfig/bufferstreams_flags.aconfig b/libs/bufferstreams/aconfig/bufferstreams_flags.aconfig
new file mode 100644
index 0000000000..e258725e3d
--- /dev/null
+++ b/libs/bufferstreams/aconfig/bufferstreams_flags.aconfig
@@ -0,0 +1,65 @@
+package: "com.android.graphics.bufferstreams.flags"
+
+flag {
+ name: "bufferstreams_steel_thread"
+ namespace: "core_graphics"
+ description: "Flag for bufferstreams steel thread milestone"
+ bug: "296101122"
+}
+
+flag {
+ name: "bufferstreams_local"
+ namespace: "core_graphics"
+ description: "Flag for bufferstreams single-process functionality milestone"
+ bug: "296100790"
+}
+
+flag {
+ name: "bufferstreams_pooling"
+ namespace: "core_graphics"
+ description: "Flag for bufferstreams buffer pooling milestone"
+ bug: "296101127"
+}
+
+flag {
+ name: "bufferstreams_ipc"
+ namespace: "core_graphics"
+ description: "Flag for bufferstreams IPC milestone"
+ bug: "296099728"
+}
+
+flag {
+ name: "bufferstreams_cpp"
+ namespace: "core_graphics"
+ description: "Flag for bufferstreams C/C++ milestone"
+ bug: "296100536"
+}
+
+flag {
+ name: "bufferstreams_utils"
+ namespace: "core_graphics"
+ description: "Flag for bufferstreams extra utilities milestone"
+ bug: "285322189"
+}
+
+flag {
+ name: "bufferstreams_demo"
+ namespace: "core_graphics"
+ description: "Flag for bufferstreams demo milestone"
+ bug: "297242965"
+}
+
+flag {
+ name: "bufferstreams_perf"
+ namespace: "core_graphics"
+ description: "Flag for bufferstreams performance enhancement milestone"
+ bug: "297242843"
+}
+
+flag {
+ name: "bufferstreams_tooling"
+ namespace: "core_graphics"
+ description: "Flag for bufferstreams tooling milestone"
+ bug: "297243180"
+}
+
diff --git a/libs/bufferstreams/examples/app/Android.bp b/libs/bufferstreams/examples/app/Android.bp
new file mode 100644
index 0000000000..bb573c596c
--- /dev/null
+++ b/libs/bufferstreams/examples/app/Android.bp
@@ -0,0 +1,49 @@
+// Copyright (C) 2023 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.
+
+android_app {
+ name: "BufferStreamsDemoApp",
+ srcs: ["java/**/*.kt"],
+ sdk_version: "current",
+
+ jni_uses_platform_apis: true,
+ jni_libs: ["libbufferstreamdemoapp"],
+ use_embedded_native_libs: true,
+ kotlincflags: [
+ "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
+ ],
+ optimize: {
+ proguard_flags_files: ["proguard-rules.pro"],
+ },
+
+ resource_dirs: ["res"],
+
+ static_libs: [
+ "androidx.activity_activity-compose",
+ "androidx.appcompat_appcompat",
+ "androidx.compose.foundation_foundation",
+ "androidx.compose.material3_material3",
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.ui_ui",
+ "androidx.compose.ui_ui-graphics",
+ "androidx.compose.ui_ui-tooling-preview",
+ "androidx.core_core-ktx",
+ "androidx.lifecycle_lifecycle-runtime-ktx",
+ "androidx.navigation_navigation-common-ktx",
+ "androidx.navigation_navigation-compose",
+ "androidx.navigation_navigation-fragment-ktx",
+ "androidx.navigation_navigation-runtime-ktx",
+ "androidx.navigation_navigation-ui-ktx",
+ ],
+}
diff --git a/libs/bufferstreams/examples/app/AndroidManifest.xml b/libs/bufferstreams/examples/app/AndroidManifest.xml
new file mode 100644
index 0000000000..a5e2fa8a63
--- /dev/null
+++ b/libs/bufferstreams/examples/app/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.graphics.bufferstreamsdemoapp"
+ xmlns:tools="http://schemas.android.com/tools">
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/Theme.Jetpack"
+ tools:targetApi="34">
+ <activity
+ android:name=".MainActivity"
+ android:exported="true"
+ android:label="@string/app_name"
+ android:theme="@style/Theme.Jetpack">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest> \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferDemosAppBar.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferDemosAppBar.kt
new file mode 100644
index 0000000000..ff3ae5a090
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferDemosAppBar.kt
@@ -0,0 +1,40 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+
+@Composable
+fun BufferDemosAppBar(
+ currentScreen: BufferDemoScreen,
+ canNavigateBack: Boolean,
+ navigateUp: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ TopAppBar(
+ title = { Text(stringResource(currentScreen.title)) },
+ colors =
+ TopAppBarDefaults.mediumTopAppBarColors(
+ containerColor = MaterialTheme.colorScheme.primaryContainer
+ ),
+ modifier = modifier,
+ navigationIcon = {
+ if (canNavigateBack) {
+ IconButton(onClick = navigateUp) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = stringResource(R.string.back_button)
+ )
+ }
+ }
+ }
+ )
+} \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt
new file mode 100644
index 0000000000..ede77938de
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt
@@ -0,0 +1,27 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+class BufferStreamJNI {
+ // Used to load the 'bufferstreamsdemoapp' library on application startup.
+ init {
+ System.loadLibrary("bufferstreamdemoapp")
+ }
+
+ /**
+ * A native method that is implemented by the 'bufferstreamsdemoapp' native library, which is
+ * packaged with this application.
+ */
+ external fun stringFromJNI(): String;
+ external fun testBufferQueueCreation();
+
+ companion object {
+ fun companion_stringFromJNI(): String {
+ val instance = BufferStreamJNI()
+ return instance.stringFromJNI()
+ }
+
+ fun companion_testBufferQueueCreation() {
+ val instance = BufferStreamJNI()
+ return instance.testBufferQueueCreation()
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt
new file mode 100644
index 0000000000..95e415ecd5
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt
@@ -0,0 +1,35 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Button
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun DemoScreen1(modifier: Modifier = Modifier) {
+ Column(modifier = modifier, verticalArrangement = Arrangement.SpaceBetween) {
+ LogOutput.getInstance().LogOutputComposable()
+ Row(modifier = Modifier.weight(1f, false).padding(16.dp)) {
+ Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
+ Button(
+ modifier = Modifier.fillMaxWidth(),
+ onClick = { BufferStreamJNI.companion_testBufferQueueCreation() }) {
+ Text("Run")
+ }
+
+ OutlinedButton(
+ modifier = Modifier.fillMaxWidth(),
+ onClick = { LogOutput.getInstance().clearText() }) {
+ Text("Clear")
+ }
+ }
+ }
+ }
+}
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen2.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen2.kt
new file mode 100644
index 0000000000..5efee92f76
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen2.kt
@@ -0,0 +1,7 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+import androidx.compose.runtime.Composable
+
+@Composable
+fun DemoScreen2() {
+} \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen3.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen3.kt
new file mode 100644
index 0000000000..8cba857737
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen3.kt
@@ -0,0 +1,7 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+import androidx.compose.runtime.Composable
+
+@Composable
+fun DemoScreen3() {
+} \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/LogOutput.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/LogOutput.kt
new file mode 100644
index 0000000000..3f0926f497
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/LogOutput.kt
@@ -0,0 +1,65 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Card
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import java.util.Collections
+
+/*
+LogOutput centralizes logging: storing, displaying, adding, and clearing log messages with
+thread safety. It is a singleton that's also accessed from C++. The private constructor will
+not allow this class to be initialized, limiting it to getInstance().
+ */
+class LogOutput private constructor() {
+ val logs = Collections.synchronizedList(mutableStateListOf<String>())
+
+ @Composable
+ fun LogOutputComposable() {
+ val rlogs = remember { logs }
+
+ Card(modifier = Modifier.fillMaxWidth().padding(16.dp).height(400.dp)) {
+ Column(
+ modifier =
+ Modifier.padding(10.dp).size(380.dp).verticalScroll(rememberScrollState())) {
+ for (log in rlogs) {
+ Text(log, modifier = Modifier.padding(0.dp))
+ }
+ }
+ }
+ }
+
+ fun clearText() {
+ logs.clear()
+ }
+
+ fun addLog(log: String) {
+ logs.add(log)
+ }
+
+ companion object {
+ @Volatile private var instance: LogOutput? = null
+
+ @JvmStatic
+ fun getInstance(): LogOutput {
+ if (instance == null) {
+ synchronized(this) {
+ if (instance == null) {
+ instance = LogOutput()
+ }
+ }
+ }
+ return instance!!
+ }
+ }
+}
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt
new file mode 100644
index 0000000000..2ccd8d75ef
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt
@@ -0,0 +1,132 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.annotation.StringRes
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.currentBackStackEntryAsState
+import androidx.navigation.compose.rememberNavController
+import com.android.graphics.bufferstreamsdemoapp.ui.theme.JetpackTheme
+import java.util.*
+
+class MainActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContent {
+ JetpackTheme {
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ color = MaterialTheme.colorScheme.background) {
+ BufferDemosApp()
+ }
+ }
+ }
+ }
+}
+
+enum class BufferDemoScreen(val route: String, @StringRes val title: Int) {
+ Start(route = "start", title = R.string.start),
+ Demo1(route = "demo1", title = R.string.demo1),
+ Demo2(route = "demo2", title = R.string.demo2),
+ Demo3(route = "demo3", title = R.string.demo3);
+
+ companion object {
+ fun findByRoute(route: String): BufferDemoScreen {
+ return values().find { it.route == route }!!
+ }
+ }
+}
+
+@Composable
+fun BufferDemosApp() {
+ var navController: NavHostController = rememberNavController()
+ // Get current back stack entry
+ val backStackEntry by navController.currentBackStackEntryAsState()
+ // Get the name of the current screen
+ val currentScreen =
+ BufferDemoScreen.findByRoute(
+ backStackEntry?.destination?.route ?: BufferDemoScreen.Start.route)
+
+ Scaffold(
+ topBar = {
+ BufferDemosAppBar(
+ currentScreen = currentScreen,
+ canNavigateBack = navController.previousBackStackEntry != null,
+ navigateUp = { navController.navigateUp() })
+ }) {
+ NavHost(
+ navController = navController,
+ startDestination = BufferDemoScreen.Start.route,
+ modifier = Modifier.padding(10.dp)) {
+ composable(route = BufferDemoScreen.Start.route) {
+ DemoList(
+ onButtonClicked = { navController.navigate(it) },
+ )
+ }
+ composable(route = BufferDemoScreen.Demo1.route) {
+ DemoScreen1(modifier = Modifier.fillMaxHeight().padding(top = 100.dp))
+ }
+ composable(route = BufferDemoScreen.Demo2.route) { DemoScreen2() }
+ composable(route = BufferDemoScreen.Demo3.route) { DemoScreen3() }
+ }
+ }
+}
+
+@Composable
+fun DemoList(onButtonClicked: (String) -> Unit) {
+ var modifier = Modifier.fillMaxSize().padding(16.dp)
+
+ Column(modifier = modifier, verticalArrangement = Arrangement.SpaceBetween) {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(8.dp)) {
+ Spacer(modifier = Modifier.height(100.dp))
+ Text(text = "Buffer Demos", style = MaterialTheme.typography.titleLarge)
+ Spacer(modifier = Modifier.height(8.dp))
+ }
+ Row(modifier = Modifier.weight(2f, false)) {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(16.dp)) {
+ for (item in BufferDemoScreen.values()) {
+ if (item.route != BufferDemoScreen.Start.route)
+ SelectDemoButton(
+ name = stringResource(item.title),
+ onClick = { onButtonClicked(item.route) })
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun SelectDemoButton(name: String, onClick: () -> Unit, modifier: Modifier = Modifier) {
+ Button(onClick = onClick, modifier = modifier.widthIn(min = 250.dp)) { Text(name) }
+}
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Color.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Color.kt
new file mode 100644
index 0000000000..d85ea724de
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Color.kt
@@ -0,0 +1,11 @@
+package com.android.graphics.bufferstreamsdemoapp.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Purple80 = Color(0xFFD0BCFF)
+val PurpleGrey80 = Color(0xFFCCC2DC)
+val Pink80 = Color(0xFFEFB8C8)
+
+val Purple40 = Color(0xFF6650a4)
+val PurpleGrey40 = Color(0xFF625b71)
+val Pink40 = Color(0xFF7D5260) \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Theme.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Theme.kt
new file mode 100644
index 0000000000..fccd93a10b
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Theme.kt
@@ -0,0 +1,60 @@
+package com.android.graphics.bufferstreamsdemoapp.ui.theme
+
+import android.app.Activity
+import android.os.Build
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalView
+import androidx.core.view.WindowCompat
+
+private val DarkColorScheme = darkColorScheme(
+ primary = Purple80,
+ secondary = PurpleGrey80,
+ tertiary = Pink80
+)
+
+private val LightColorScheme = lightColorScheme(
+ primary = Purple40,
+ secondary = PurpleGrey40,
+ tertiary = Pink40
+)
+
+@Composable
+fun JetpackTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ // Dynamic color is available on Android 12+
+ dynamicColor: Boolean = true,
+ content: @Composable () -> Unit
+) {
+ val colorScheme = when {
+ dynamicColor -> {
+ val context = LocalContext.current
+ if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+ }
+
+ darkTheme -> DarkColorScheme
+ else -> LightColorScheme
+ }
+ val view = LocalView.current
+ if (!view.isInEditMode) {
+ SideEffect {
+ val window = (view.context as Activity).window
+ window.statusBarColor = colorScheme.primary.toArgb()
+ WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
+ }
+ }
+
+ MaterialTheme(
+ colorScheme = colorScheme,
+ typography = Typography,
+ content = content
+ )
+} \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Type.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Type.kt
new file mode 100644
index 0000000000..06814ead8b
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Type.kt
@@ -0,0 +1,18 @@
+package com.android.graphics.bufferstreamsdemoapp.ui.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+ bodyLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.5.sp
+ )
+) \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/jni/Android.bp b/libs/bufferstreams/examples/app/jni/Android.bp
new file mode 100644
index 0000000000..67910a1c4d
--- /dev/null
+++ b/libs/bufferstreams/examples/app/jni/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2023 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.
+
+cc_library_shared {
+ name: "libbufferstreamdemoapp",
+ cflags: [
+ "-Werror",
+ "-Wno-error=unused-parameter",
+ ],
+ shared_libs: [
+ "libgui",
+ "libbase",
+ "libutils",
+ ],
+ header_libs: ["jni_headers"],
+ srcs: ["*.cpp"],
+}
diff --git a/libs/bufferstreams/examples/app/jni/main.cpp b/libs/bufferstreams/examples/app/jni/main.cpp
new file mode 100644
index 0000000000..550ad22ae8
--- /dev/null
+++ b/libs/bufferstreams/examples/app/jni/main.cpp
@@ -0,0 +1,53 @@
+// Copyright (C) 2023 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.
+
+#include <jni.h>
+#include <string>
+
+#include <gui/BufferQueue.h>
+
+void log(JNIEnv* env, std::string l) {
+ jclass clazz = env->FindClass("com/android/graphics/bufferstreamsdemoapp/LogOutput");
+ jmethodID getInstance = env->GetStaticMethodID(clazz, "getInstance",
+ "()Lcom/android/graphics/bufferstreamsdemoapp/LogOutput;");
+ jmethodID addLog = env->GetMethodID(clazz, "addLog", "(Ljava/lang/String;)V");
+ jobject dmg = env->CallStaticObjectMethod(clazz, getInstance);
+
+ jstring jlog = env->NewStringUTF(l.c_str());
+ env->CallVoidMethod(dmg, addLog, jlog);
+}
+
+extern "C" {
+
+JNIEXPORT jstring JNICALL
+Java_com_android_graphics_bufferstreamsdemoapp_BufferStreamJNI_stringFromJNI(JNIEnv* env,
+ jobject /* this */) {
+ const char* hello = "Hello from C++";
+ return env->NewStringUTF(hello);
+}
+
+JNIEXPORT void JNICALL
+Java_com_android_graphics_bufferstreamsdemoapp_BufferStreamJNI_testBufferQueueCreation(
+ JNIEnv* env, jobject /* thiz */) {
+
+ log(env, "Calling testBufferQueueCreation.");
+ android::sp<android::IGraphicBufferProducer> producer;
+ log(env, "Created producer.");
+ android::sp<android::IGraphicBufferConsumer> consumer;
+ log(env, "Created consumer.");
+ android::BufferQueue::createBufferQueue(&producer, &consumer);
+ log(env, "Created BufferQueue successfully.");
+ log(env, "Done!");
+}
+} \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/proguard-rules.pro b/libs/bufferstreams/examples/app/proguard-rules.pro
new file mode 100644
index 0000000000..7a987fc7c4
--- /dev/null
+++ b/libs/bufferstreams/examples/app/proguard-rules.pro
@@ -0,0 +1,23 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+
+-keep,allowoptimization,allowobfuscation class com.android.graphics.bufferstreamsdemoapp.** { *; }
diff --git a/libs/bufferstreams/examples/app/res/drawable/ic_launcher_background.xml b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000000..07d5da9cbf
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:fillColor="#3DDC84"
+ android:pathData="M0,0h108v108h-108z" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M9,0L9,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,0L19,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,0L29,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,0L39,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,0L49,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,0L59,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,0L69,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,0L79,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M89,0L89,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M99,0L99,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,9L108,9"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,19L108,19"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,29L108,29"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,39L108,39"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,49L108,49"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,59L108,59"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,69L108,69"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,79L108,79"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,89L108,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,99L108,99"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,29L89,29"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,39L89,39"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,49L89,49"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,59L89,59"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,69L89,69"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,79L89,79"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,19L29,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,19L39,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,19L49,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,19L59,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,19L69,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,19L79,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+</vector>
diff --git a/libs/bufferstreams/examples/app/res/drawable/ic_launcher_foreground.xml b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000000..2b068d1146
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:endX="85.84757"
+ android:endY="92.4963"
+ android:startX="42.9492"
+ android:startY="49.59793"
+ android:type="linear">
+ <item
+ android:color="#44000000"
+ android:offset="0.0" />
+ <item
+ android:color="#00000000"
+ android:offset="1.0" />
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:fillColor="#FFFFFF"
+ android:fillType="nonZero"
+ android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+ android:strokeWidth="1"
+ android:strokeColor="#00000000" />
+</vector> \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher.xml b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher.xml
new file mode 100644
index 0000000000..6f3b755bf5
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+ <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon> \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher_round.xml b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher_round.xml
new file mode 100644
index 0000000000..6f3b755bf5
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+ <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon> \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000000..c209e78ecd
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..b2dfe3d1ba
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000000..4f0f1d64e5
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..62b611da08
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000000..948a3070fe
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..1b9a6956b3
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000000..28d4b77f9f
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..9287f50836
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000000..aa7d6427e6
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..9126ae37cb
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/values/colors.xml b/libs/bufferstreams/examples/app/res/values/colors.xml
new file mode 100644
index 0000000000..f8c6127d32
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/values/colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="purple_200">#FFBB86FC</color>
+ <color name="purple_500">#FF6200EE</color>
+ <color name="purple_700">#FF3700B3</color>
+ <color name="teal_200">#FF03DAC5</color>
+ <color name="teal_700">#FF018786</color>
+ <color name="black">#FF000000</color>
+ <color name="white">#FFFFFFFF</color>
+</resources> \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/values/strings.xml b/libs/bufferstreams/examples/app/res/values/strings.xml
new file mode 100644
index 0000000000..75c8ab5e1c
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/values/strings.xml
@@ -0,0 +1,8 @@
+<resources>
+ <string name="app_name">Buffer Demos</string>
+ <string name="start">Start</string>
+ <string name="demo1">Demo 1</string>
+ <string name="demo2">Demo 2</string>
+ <string name="demo3">Demo 3</string>
+ <string name="back_button">Back</string>
+</resources> \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/values/themes.xml b/libs/bufferstreams/examples/app/res/values/themes.xml
new file mode 100644
index 0000000000..eeb308ae44
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/values/themes.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style name="Theme.Jetpack" parent="android:Theme.Material.Light.NoActionBar" />
+</resources> \ No newline at end of file
diff --git a/libs/bufferstreams/include/bufferstreams.h b/libs/bufferstreams/include/bufferstreams.h
new file mode 100644
index 0000000000..5308de24c0
--- /dev/null
+++ b/libs/bufferstreams/include/bufferstreams.h
@@ -0,0 +1,13 @@
+/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+
+/**
+ * This function will print Hello World.
+ */
+bool hello(void);
diff --git a/libs/bufferstreams/rust/Android.bp b/libs/bufferstreams/rust/Android.bp
new file mode 100644
index 0000000000..7fcb222085
--- /dev/null
+++ b/libs/bufferstreams/rust/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2023 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.
+
+rust_defaults {
+ name: "libbufferstreams_defaults",
+ srcs: ["src/lib.rs"],
+ rustlibs: [
+ "libanyhow",
+ "libnativewindow_rs",
+ ],
+ edition: "2021",
+}
+
+rust_library {
+ name: "libbufferstreams",
+ crate_name: "bufferstreams",
+ defaults: ["libbufferstreams_defaults"],
+ min_sdk_version: "30",
+}
+
+rust_test {
+ name: "libbufferstreams-internal_test",
+ crate_name: "bufferstreams",
+ defaults: ["libbufferstreams_defaults"],
+ test_suites: ["general-tests"],
+}
diff --git a/libs/bufferstreams/rust/Cargo.lock b/libs/bufferstreams/rust/Cargo.lock
new file mode 100644
index 0000000000..4482dba6cd
--- /dev/null
+++ b/libs/bufferstreams/rust/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "bufferstreams"
+version = "0.1.0"
diff --git a/libs/bufferstreams/rust/Cargo.toml b/libs/bufferstreams/rust/Cargo.toml
new file mode 100644
index 0000000000..d30c55c551
--- /dev/null
+++ b/libs/bufferstreams/rust/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bufferstreams"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
diff --git a/libs/bufferstreams/rust/cbindgen.toml b/libs/bufferstreams/rust/cbindgen.toml
new file mode 100644
index 0000000000..eda837f360
--- /dev/null
+++ b/libs/bufferstreams/rust/cbindgen.toml
@@ -0,0 +1,149 @@
+# See https://github.com/eqrion/cbindgen/blob/master/docs.md#cbindgentoml
+# for detailed documentation of every option here.
+
+
+
+language = "C"
+
+
+
+############## Options for Wrapping the Contents of the Header #################
+
+# header = "/* Text to put at the beginning of the generated file. Probably a license. */"
+# trailer = "/* Text to put at the end of the generated file */"
+# include_guard = "my_bindings_h"
+# pragma_once = true
+autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
+include_version = false
+# namespace = "my_namespace"
+namespaces = []
+using_namespaces = []
+sys_includes = []
+includes = []
+no_includes = false
+after_includes = ""
+
+
+
+
+############################ Code Style Options ################################
+
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+documentation = true
+documentation_style = "auto"
+documentation_length = "full"
+line_endings = "LF" # also "CR", "CRLF", "Native"
+
+
+
+
+############################# Codegen Options ##################################
+
+style = "both"
+sort_by = "Name" # default for `fn.sort_by` and `const.sort_by`
+usize_is_size_t = true
+
+
+
+[defines]
+# "target_os = freebsd" = "DEFINE_FREEBSD"
+# "feature = serde" = "DEFINE_SERDE"
+
+
+
+[export]
+include = []
+exclude = []
+# prefix = "CAPI_"
+item_types = []
+renaming_overrides_prefixing = false
+
+
+
+[export.rename]
+
+
+
+[export.body]
+
+
+[export.mangle]
+
+
+[fn]
+rename_args = "None"
+# must_use = "MUST_USE_FUNC"
+# no_return = "NO_RETURN"
+# prefix = "START_FUNC"
+# postfix = "END_FUNC"
+args = "auto"
+sort_by = "Name"
+
+
+
+
+[struct]
+rename_fields = "None"
+# must_use = "MUST_USE_STRUCT"
+derive_constructor = false
+derive_eq = false
+derive_neq = false
+derive_lt = false
+derive_lte = false
+derive_gt = false
+derive_gte = false
+
+
+
+
+[enum]
+rename_variants = "None"
+# must_use = "MUST_USE_ENUM"
+add_sentinel = false
+prefix_with_name = false
+derive_helper_methods = false
+derive_const_casts = false
+derive_mut_casts = false
+# cast_assert_name = "ASSERT"
+derive_tagged_enum_destructor = false
+derive_tagged_enum_copy_constructor = false
+enum_class = true
+private_default_tagged_enum_constructor = false
+
+
+
+
+[const]
+allow_static_const = true
+allow_constexpr = false
+sort_by = "Name"
+
+
+
+
+[macro_expansion]
+bitflags = false
+
+
+
+
+
+
+############## Options for How Your Rust library Should Be Parsed ##############
+
+[parse]
+parse_deps = false
+# include = []
+exclude = []
+clean = false
+extra_bindings = []
+
+
+
+[parse.expand]
+crates = []
+all_features = false
+default_features = true
+features = [] \ No newline at end of file
diff --git a/libs/bufferstreams/rust/src/buffers/buffer.rs b/libs/bufferstreams/rust/src/buffers/buffer.rs
new file mode 100644
index 0000000000..0a8516e8e3
--- /dev/null
+++ b/libs/bufferstreams/rust/src/buffers/buffer.rs
@@ -0,0 +1,80 @@
+// Copyright (C) 2023 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.
+
+//! Wrapper around the HardwareBuffer
+
+use nativewindow::*;
+
+use super::{buffer_owner::NoBufferOwner, BufferOwner};
+
+/// A wrapper for a hardware buffer.
+///
+/// This buffer may be associated with a buffer pool to which it will be returned to it when dropped.
+pub struct Buffer {
+ buffer_owner: Box<dyn BufferOwner>,
+ hardware_buffer: HardwareBuffer,
+}
+
+impl Buffer {
+ /// Create new buffer with a custom [BufferOwner].
+ pub fn new(buffer_owner: Box<dyn BufferOwner>, hardware_buffer: HardwareBuffer) -> Self {
+ Self { buffer_owner, hardware_buffer }
+ }
+
+ /// Create a new buffer with no association to any buffer pool.
+ pub fn new_unowned(hardware_buffer: HardwareBuffer) -> Self {
+ Self { buffer_owner: Box::new(NoBufferOwner), hardware_buffer }
+ }
+
+ /// Get the id of the underlying buffer.
+ pub fn id(&self) -> u64 {
+ self.hardware_buffer.id()
+ }
+
+ /// Get a reference to the underlying hardware buffer.
+ pub fn buffer(&self) -> &HardwareBuffer {
+ &self.hardware_buffer
+ }
+}
+
+impl Drop for Buffer {
+ fn drop(&mut self) {
+ self.buffer_owner.on_return(self);
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use crate::StreamConfig;
+
+ const STREAM_CONFIG: StreamConfig = StreamConfig {
+ width: 1,
+ height: 1,
+ layers: 1,
+ format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+ stride: 0,
+ };
+
+ #[test]
+ fn test_get_buffer_id() {
+ let hardware_buffer = STREAM_CONFIG.create_hardware_buffer().unwrap();
+ let buffer_id = hardware_buffer.id();
+
+ let buffer = Buffer::new_unowned(hardware_buffer);
+ assert_eq!(buffer_id, buffer.id());
+ }
+}
diff --git a/libs/bufferstreams/rust/src/buffers/buffer_owner.rs b/libs/bufferstreams/rust/src/buffers/buffer_owner.rs
new file mode 100644
index 0000000000..a4abb9d3b7
--- /dev/null
+++ b/libs/bufferstreams/rust/src/buffers/buffer_owner.rs
@@ -0,0 +1,28 @@
+// Copyright (C) 2023 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.
+
+use super::Buffer;
+
+/// Trait that represents an owner of a buffer that might need to handle events such as a buffer
+/// being dropped.
+pub trait BufferOwner {
+ /// Called when a buffer is dropped.
+ fn on_return(&self, buffer: &Buffer);
+}
+
+pub(super) struct NoBufferOwner;
+
+impl BufferOwner for NoBufferOwner {
+ fn on_return(&self, _buffer: &Buffer) {}
+}
diff --git a/libs/bufferstreams/rust/src/buffers/buffer_pool.rs b/libs/bufferstreams/rust/src/buffers/buffer_pool.rs
new file mode 100644
index 0000000000..05804e2e3a
--- /dev/null
+++ b/libs/bufferstreams/rust/src/buffers/buffer_pool.rs
@@ -0,0 +1,137 @@
+// Copyright (C) 2023 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.
+
+//! A Buffer Pool containing and managing HardwareBuffers
+
+use std::{
+ collections::HashMap,
+ sync::{Arc, Mutex, Weak},
+};
+
+use nativewindow::*;
+
+use crate::StreamConfig;
+
+use super::{Buffer, BufferOwner};
+
+pub(super) struct BufferPoolInner {
+ size: usize,
+ hardware_buffers: HashMap<u64, HardwareBuffer>,
+ available_buffers: Vec<u64>,
+}
+
+impl BufferPoolInner {
+ pub(super) fn return_buffer(&mut self, buffer_id: u64) {
+ assert!(self.hardware_buffers.contains_key(&buffer_id));
+ assert!(!self.available_buffers.contains(&buffer_id));
+
+ self.available_buffers.push(buffer_id);
+ }
+}
+
+struct BufferPoolOwner(Weak<Mutex<BufferPoolInner>>);
+
+impl BufferOwner for BufferPoolOwner {
+ fn on_return(&self, buffer: &Buffer) {
+ if let Some(locked_buffer_pool) = self.0.upgrade() {
+ let mut buffer_pool = locked_buffer_pool.lock().unwrap();
+
+ buffer_pool.return_buffer(buffer.id());
+ }
+ }
+}
+
+/// A thread-safe collection of buffers.
+///
+/// A buffer pool can be of arbitrary size. It creates and then holds references to all buffers
+/// associated with it.
+pub struct BufferPool(Arc<Mutex<BufferPoolInner>>);
+
+impl BufferPool {
+ /// Creates a new buffer pool of size pool_size. All buffers will be created according to
+ /// the stream config.
+ ///
+ /// This constructor creates all buffers at initialization.
+ pub fn new(pool_size: usize, stream_config: StreamConfig) -> Option<Self> {
+ let mut hardware_buffers = HashMap::new();
+ let mut available_buffers = Vec::new();
+ for _ in 0..pool_size {
+ if let Some(buffer) = stream_config.create_hardware_buffer() {
+ available_buffers.push(buffer.id());
+ hardware_buffers.insert(buffer.id(), buffer);
+ } else {
+ return None;
+ }
+ }
+ Some(Self(Arc::new(Mutex::new(BufferPoolInner {
+ size: pool_size,
+ hardware_buffers,
+ available_buffers,
+ }))))
+ }
+
+ /// Try to acquire the next available buffer in the buffer pool.
+ ///
+ /// If all buffers are in use it will return None.
+ pub fn next_buffer(&mut self) -> Option<Buffer> {
+ let mut inner = self.0.lock().unwrap();
+ if let Some(buffer_id) = inner.available_buffers.pop() {
+ Some(Buffer::new(
+ Box::new(BufferPoolOwner(Arc::downgrade(&self.0))),
+ inner.hardware_buffers[&buffer_id].clone(),
+ ))
+ } else {
+ None
+ }
+ }
+
+ /// Gets the size of the buffer pool.
+ pub fn size(&self) -> usize {
+ let inner = self.0.lock().unwrap();
+ inner.size
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ const STREAM_CONFIG: StreamConfig = StreamConfig {
+ width: 1,
+ height: 1,
+ layers: 1,
+ format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+ stride: 0,
+ };
+
+ #[test]
+ fn buffer_pool_next_buffer() {
+ let mut buffer_pool = BufferPool::new(1, STREAM_CONFIG).unwrap();
+ let next_buffer = buffer_pool.next_buffer();
+
+ assert!(next_buffer.is_some());
+ assert!(buffer_pool.next_buffer().is_none());
+ }
+
+ #[test]
+ fn drop_buffer_returns_to_pool() {
+ let mut buffer_pool = BufferPool::new(1, STREAM_CONFIG).unwrap();
+ let next_buffer = buffer_pool.next_buffer();
+
+ assert!(next_buffer.is_some());
+ drop(next_buffer);
+ assert!(buffer_pool.next_buffer().is_some());
+ }
+}
diff --git a/libs/bufferstreams/rust/src/buffers/mod.rs b/libs/bufferstreams/rust/src/buffers/mod.rs
new file mode 100644
index 0000000000..83360d6c00
--- /dev/null
+++ b/libs/bufferstreams/rust/src/buffers/mod.rs
@@ -0,0 +1,23 @@
+// Copyright (C) 2023 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.
+
+//! Module containing Buffers and BufferPools
+
+mod buffer;
+mod buffer_owner;
+mod buffer_pool;
+
+pub use buffer::*;
+pub use buffer_owner::*;
+pub use buffer_pool::*;
diff --git a/libs/bufferstreams/rust/src/lib.rs b/libs/bufferstreams/rust/src/lib.rs
new file mode 100644
index 0000000000..be1525d41f
--- /dev/null
+++ b/libs/bufferstreams/rust/src/lib.rs
@@ -0,0 +1,257 @@
+// Copyright (C) 2023 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.
+
+//! libbufferstreams: Reactive Streams for Graphics Buffers
+
+pub mod buffers;
+pub mod publishers;
+mod stream_config;
+pub mod subscribers;
+pub mod subscriptions;
+
+use buffers::Buffer;
+pub use stream_config::*;
+
+use std::time::Instant;
+
+/// This function will print Hello World.
+#[no_mangle]
+pub extern "C" fn hello() -> bool {
+ println!("Hello world.");
+ true
+}
+
+/// BufferPublishers provide buffers to BufferSusbscribers. Depending on the
+/// particular object in question, these could be allocated locally or provided
+/// over IPC.
+///
+/// BufferPublishers are required to adhere to the following, based on the
+/// reactive streams specification:
+/// * The total number of on_next´s signalled by a Publisher to a Subscriber
+/// MUST be less than or equal to the total number of elements requested by that
+/// Subscriber´s Subscription at all times.
+/// * A Publisher MAY signal fewer on_next than requested and terminate the
+/// Subscription by calling on_complete or on_error.
+/// * on_subscribe, on_next, on_error and on_complete signaled to a Subscriber
+/// MUST be signaled serially.
+/// * If a Publisher fails it MUST signal an on_error.
+/// * If a Publisher terminates successfully (finite stream) it MUST signal an
+/// on_complete.
+/// * If a Publisher signals either on_error or on_complete on a Subscriber,
+/// that Subscriber’s Subscription MUST be considered cancelled.
+/// * Once a terminal state has been signaled (on_error, on_complete) it is
+/// REQUIRED that no further signals occur.
+/// * If a Subscription is cancelled its Subscriber MUST eventually stop being
+/// signaled.
+/// * A Publisher MAY support multiple Subscribers and decides whether each
+/// Subscription is unicast or multicast.
+pub trait BufferPublisher {
+ /// Returns the StreamConfig of buffers that publisher creates.
+ fn get_publisher_stream_config(&self) -> StreamConfig;
+ /// This function will create the subscription between the publisher and
+ /// the subscriber.
+ fn subscribe(&mut self, subscriber: impl BufferSubscriber + 'static);
+}
+
+/// BufferSubscribers can subscribe to BufferPublishers. They can request Frames
+/// via the BufferSubscription they get from the publisher, then receive Frames
+/// via on_next.
+///
+/// BufferSubcribers are required to adhere to the following, based on the
+/// reactive streams specification:
+/// * The total number of on_next´s signalled by a Publisher to a Subscriber
+/// MUST be less than or equal to the total number of elements requested by that
+/// Subscriber´s Subscription at all times.
+/// * A Publisher MAY signal fewer on_next than requested and terminate the
+/// Subscription by calling on_complete or on_error.
+/// * on_subscribe, on_next, on_error and on_complete signaled to a Subscriber
+/// MUST be signaled serially.
+/// * If a Publisher fails it MUST signal an on_error.
+/// * If a Publisher terminates successfully (finite stream) it MUST signal an
+/// on_complete.
+/// * If a Publisher signals either on_error or on_complete on a Subscriber,
+/// that Subscriber’s Subscription MUST be considered cancelled.
+/// * Once a terminal state has been signaled (on_error, on_complete) it is
+/// REQUIRED that no further signals occur.
+/// * If a Subscription is cancelled its Subscriber MUST eventually stop being
+/// signaled.
+/// * Publisher.subscribe MAY be called as many times as wanted but MUST be
+/// with a different Subscriber each time.
+/// * A Publisher MAY support multiple Subscribers and decides whether each
+/// Subscription is unicast or multicast.
+pub trait BufferSubscriber {
+ /// The StreamConfig of buffers that this subscriber expects.
+ fn get_subscriber_stream_config(&self) -> StreamConfig;
+ /// This function will be called at the beginning of the subscription.
+ fn on_subscribe(&mut self, subscription: Box<dyn BufferSubscription>);
+ /// This function will be called for buffer that comes in.
+ fn on_next(&mut self, frame: Frame);
+ /// This function will be called in case of an error.
+ fn on_error(&mut self, error: BufferError);
+ /// This function will be called on finite streams when done.
+ fn on_complete(&mut self);
+}
+
+/// BufferSubscriptions serve as the bridge between BufferPublishers and
+/// BufferSubscribers. BufferSubscribers receive a BufferSubscription when they
+/// subscribe to a BufferPublisher via on_subscribe.
+/// This object is to be used by the BufferSubscriber to cancel its subscription
+/// or request more buffers.
+///
+/// BufferSubcriptions are required to adhere to the following, based on the
+/// reactive streams specification:
+/// * Subscription.request and Subscription.cancel MUST only be called inside
+/// of its Subscriber context.
+/// * The Subscription MUST allow the Subscriber to call Subscription.request
+/// synchronously from within on_next or on_subscribe.
+/// * Subscription.request MUST place an upper bound on possible synchronous
+/// recursion between Publisher and Subscriber.
+/// * Subscription.request SHOULD respect the responsivity of its caller by
+/// returning in a timely manner.
+/// * Subscription.cancel MUST respect the responsivity of its caller by
+/// returning in a timely manner, MUST be idempotent and MUST be thread-safe.
+/// * After the Subscription is cancelled, additional
+/// Subscription.request(n: u64) MUST be NOPs.
+/// * After the Subscription is cancelled, additional Subscription.cancel()
+/// MUST be NOPs.
+/// * While the Subscription is not cancelled, Subscription.request(n: u64)
+/// MUST register the given number of additional elements to be produced to the
+/// respective subscriber.
+/// * While the Subscription is not cancelled, Subscription.request(n: u64)
+/// MUST signal on_error if the argument is <= 0. The cause message SHOULD
+/// explain that non-positive request signals are illegal.
+/// * While the Subscription is not cancelled, Subscription.request(n: u64)
+/// MAY synchronously call on_next on this (or other) subscriber(s).
+/// * While the Subscription is not cancelled, Subscription.request(n: u64)
+/// MAY synchronously call on_complete or on_error on this (or other)
+/// subscriber(s).
+/// * While the Subscription is not cancelled, Subscription.cancel() MUST
+/// request the Publisher to eventually stop signaling its Subscriber. The
+/// operation is NOT REQUIRED to affect the Subscription immediately.
+/// * While the Subscription is not cancelled, Subscription.cancel() MUST
+/// request the Publisher to eventually drop any references to the corresponding
+/// subscriber.
+/// * While the Subscription is not cancelled, calling Subscription.cancel MAY
+/// cause the Publisher, if stateful, to transition into the shut-down state if
+/// no other Subscription exists at this point.
+/// * Calling Subscription.cancel MUST return normally.
+/// * Calling Subscription.request MUST return normally.
+pub trait BufferSubscription {
+ /// request
+ fn request(&self, n: u64);
+ /// cancel
+ fn cancel(&self);
+}
+
+/// Type used to describe errors produced by subscriptions.
+pub type BufferError = anyhow::Error;
+
+/// Struct used to contain the buffer.
+pub struct Frame {
+ /// A buffer to be used this frame.
+ pub buffer: Buffer,
+ /// The time at which the buffer was dispatched.
+ pub present_time: Instant,
+ /// A fence used for reading/writing safely.
+ pub fence: i32,
+}
+
+#[cfg(test)]
+mod test {
+ #![allow(warnings, unused)]
+ use super::*;
+
+ use anyhow::anyhow;
+ use buffers::Buffer;
+ use nativewindow::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
+ use std::borrow::BorrowMut;
+ use std::error::Error;
+ use std::ops::Add;
+ use std::sync::Arc;
+ use std::time::Duration;
+
+ use crate::publishers::testing::*;
+ use crate::subscribers::{testing::*, SharedSubscriber};
+
+ const STREAM_CONFIG: StreamConfig = StreamConfig {
+ width: 1,
+ height: 1,
+ layers: 1,
+ format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+ stride: 0,
+ };
+
+ fn make_frame() -> Frame {
+ Frame {
+ buffer: Buffer::new_unowned(
+ STREAM_CONFIG
+ .create_hardware_buffer()
+ .expect("Unable to create hardware buffer for test"),
+ ),
+ present_time: Instant::now() + Duration::from_secs(1),
+ fence: 0,
+ }
+ }
+
+ #[test]
+ fn test_test_implementations_next() {
+ let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG));
+ let mut publisher = TestPublisher::new(STREAM_CONFIG);
+
+ publisher.subscribe(subscriber.clone());
+ assert!(subscriber.map_inner(|s| s.has_subscription()));
+ assert!(publisher.has_subscriber());
+
+ publisher.send_frame(make_frame());
+ let events = subscriber.map_inner_mut(|s| s.take_events());
+ assert!(!matches!(events.last().unwrap(), TestingSubscriberEvent::Next(_)));
+
+ subscriber.map_inner(|s| s.request(1));
+ assert_eq!(publisher.pending_requests(), 1);
+
+ publisher.send_frame(make_frame());
+ let events = subscriber.map_inner_mut(|s| s.take_events());
+ assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Next(_)));
+ assert_eq!(publisher.pending_requests(), 0);
+ }
+
+ #[test]
+ fn test_test_implementations_complete() {
+ let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG));
+ let mut publisher = TestPublisher::new(STREAM_CONFIG);
+
+ publisher.subscribe(subscriber.clone());
+ assert!(subscriber.map_inner(|s| s.has_subscription()));
+ assert!(publisher.has_subscriber());
+
+ publisher.send_complete();
+ let events = subscriber.map_inner_mut(|s| s.take_events());
+ assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Complete));
+ }
+
+ #[test]
+ fn test_test_implementations_error() {
+ let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG));
+ let mut publisher = TestPublisher::new(STREAM_CONFIG);
+
+ publisher.subscribe(subscriber.clone());
+ assert!(subscriber.map_inner(|s| s.has_subscription()));
+ assert!(publisher.has_subscriber());
+
+ publisher.send_error(anyhow!("error"));
+ let events = subscriber.map_inner_mut(|s| s.take_events());
+ assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Error(_)));
+ }
+}
diff --git a/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs b/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs
new file mode 100644
index 0000000000..846105dacd
--- /dev/null
+++ b/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs
@@ -0,0 +1,112 @@
+// Copyright (C) 2023 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.
+
+//!
+
+use std::time::Instant;
+
+use crate::{
+ buffers::BufferPool, subscriptions::SharedBufferSubscription, BufferPublisher,
+ BufferSubscriber, Frame, StreamConfig,
+};
+
+/// The [BufferPoolPublisher] submits buffers from a pool over to the subscriber.
+pub struct BufferPoolPublisher {
+ stream_config: StreamConfig,
+ buffer_pool: BufferPool,
+ subscription: SharedBufferSubscription,
+ subscriber: Option<Box<dyn BufferSubscriber>>,
+}
+
+impl BufferPoolPublisher {
+ /// The [BufferPoolPublisher] needs to initialize a [BufferPool], the [BufferPool] will create
+ /// all buffers at initialization using the stream_config.
+ pub fn new(stream_config: StreamConfig, size: usize) -> Option<Self> {
+ BufferPool::new(size, stream_config).map(|buffer_pool| Self {
+ stream_config,
+ buffer_pool,
+ subscription: SharedBufferSubscription::new(),
+ subscriber: None,
+ })
+ }
+
+ /// If the [SharedBufferSubscription] is ready for a [Frame], a buffer will be requested from
+ /// [BufferPool] and sent over to the [BufferSubscriber].
+ pub fn send_next_frame(&mut self, present_time: Instant) -> bool {
+ if let Some(subscriber) = self.subscriber.as_mut() {
+ if self.subscription.take_request() {
+ if let Some(buffer) = self.buffer_pool.next_buffer() {
+ let frame = Frame { buffer, present_time, fence: 0 };
+
+ subscriber.on_next(frame);
+ return true;
+ }
+ }
+ }
+ false
+ }
+}
+
+impl BufferPublisher for BufferPoolPublisher {
+ fn get_publisher_stream_config(&self) -> StreamConfig {
+ self.stream_config
+ }
+
+ fn subscribe(&mut self, subscriber: impl BufferSubscriber + 'static) {
+ assert!(self.subscriber.is_none());
+
+ self.subscriber = Some(Box::new(subscriber));
+ self.subscriber.as_mut().unwrap().on_subscribe(self.subscription.clone_for_subscriber());
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use nativewindow::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
+
+ use super::*;
+
+ use crate::{
+ subscribers::{
+ testing::{TestSubscriber, TestingSubscriberEvent},
+ SharedSubscriber,
+ },
+ StreamConfig,
+ };
+
+ const STREAM_CONFIG: StreamConfig = StreamConfig {
+ width: 1,
+ height: 1,
+ layers: 1,
+ format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+ stride: 0,
+ };
+
+ #[test]
+ fn test_send_next_frame() {
+ let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG));
+
+ let mut buffer_pool_publisher = BufferPoolPublisher::new(STREAM_CONFIG, 1).unwrap();
+ buffer_pool_publisher.subscribe(subscriber.clone());
+
+ subscriber.map_inner(|s| s.request(1));
+
+ assert!(buffer_pool_publisher.send_next_frame(Instant::now()));
+
+ let events = subscriber.map_inner_mut(|s| s.take_events());
+ assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Next(_)));
+ assert_eq!(buffer_pool_publisher.subscription.pending_requests(), 0);
+ }
+}
diff --git a/libs/bufferstreams/rust/src/publishers/mod.rs b/libs/bufferstreams/rust/src/publishers/mod.rs
new file mode 100644
index 0000000000..8ed3ba0e00
--- /dev/null
+++ b/libs/bufferstreams/rust/src/publishers/mod.rs
@@ -0,0 +1,20 @@
+// Copyright (C) 2023 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.
+
+//! This module provides [BufferSubscriber] implementations and helpers.
+
+mod buffer_pool_publisher;
+pub mod testing;
+
+pub use buffer_pool_publisher::*;
diff --git a/libs/bufferstreams/rust/src/publishers/testing.rs b/libs/bufferstreams/rust/src/publishers/testing.rs
new file mode 100644
index 0000000000..1593b18d7f
--- /dev/null
+++ b/libs/bufferstreams/rust/src/publishers/testing.rs
@@ -0,0 +1,103 @@
+// Copyright (C) 2023 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.
+
+//! Provides useful publishers for testing specifically. These should not be used in normal code.
+
+use crate::{subscriptions::SharedBufferSubscription, *};
+
+/// A [BufferPublisher] specifically for testing.
+///
+/// Provides users the ability to send events and read the state of the subscription.
+pub struct TestPublisher {
+ config: StreamConfig,
+ subscriber: Option<Box<dyn BufferSubscriber>>,
+ subscription: SharedBufferSubscription,
+}
+
+impl TestPublisher {
+ /// Create a new [TestPublisher].
+ pub fn new(config: StreamConfig) -> Self {
+ Self { config, subscriber: None, subscription: SharedBufferSubscription::new() }
+ }
+
+ /// Send a [BufferSubscriber::on_next] event to an owned [BufferSubscriber] if it has any
+ /// requested and returns true. Drops the frame and returns false otherwise.
+ ///
+ /// # Panics
+ ///
+ /// This will panic if there is no owned subscriber.
+ pub fn send_frame(&mut self, frame: Frame) -> bool {
+ let subscriber =
+ self.subscriber.as_deref_mut().expect("Tried to send_frame with no subscriber");
+
+ if self.subscription.take_request() {
+ subscriber.on_next(frame);
+ true
+ } else {
+ false
+ }
+ }
+
+ /// Send a [BufferSubscriber::on_complete] event to an owned [BufferSubscriber].
+ ///
+ /// # Panics
+ ///
+ /// This will panic if there is no owned subscriber.
+ pub fn send_complete(&mut self) {
+ let subscriber =
+ self.subscriber.as_deref_mut().expect("Tried to send_complete with no subscriber");
+ subscriber.on_complete();
+ }
+
+ /// Send a [BufferSubscriber::on_error] event to an owned [BufferSubscriber].
+ ///
+ /// # Panics
+ ///
+ /// This will panic if there is no owned subscriber.
+ pub fn send_error(&mut self, error: BufferError) {
+ let subscriber =
+ self.subscriber.as_deref_mut().expect("Tried to send_error with no subscriber");
+ subscriber.on_error(error);
+ }
+
+ /// Returns whether this [BufferPublisher] owns a subscriber.
+ pub fn has_subscriber(&self) -> bool {
+ self.subscriber.is_some()
+ }
+
+ /// Returns the nummber of frames requested by the [BufferSubscriber].
+ pub fn pending_requests(&self) -> u64 {
+ self.subscription.pending_requests()
+ }
+
+ /// Returns whether the [BufferSubscriber] has cancelled the subscription.
+ pub fn is_cancelled(&self) -> bool {
+ self.subscription.is_cancelled()
+ }
+}
+
+impl BufferPublisher for TestPublisher {
+ fn get_publisher_stream_config(&self) -> crate::StreamConfig {
+ self.config
+ }
+
+ fn subscribe(&mut self, subscriber: impl BufferSubscriber + 'static) {
+ assert!(self.subscriber.is_none(), "TestingPublishers can only take one subscriber");
+ self.subscriber = Some(Box::new(subscriber));
+
+ if let Some(ref mut subscriber) = self.subscriber {
+ subscriber.on_subscribe(self.subscription.clone_for_subscriber());
+ }
+ }
+}
diff --git a/libs/bufferstreams/rust/src/stream_config.rs b/libs/bufferstreams/rust/src/stream_config.rs
new file mode 100644
index 0000000000..454bdf144e
--- /dev/null
+++ b/libs/bufferstreams/rust/src/stream_config.rs
@@ -0,0 +1,67 @@
+// Copyright (C) 2023 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.
+
+use nativewindow::*;
+
+/// The configuration of the buffers published by a [BufferPublisher] or
+/// expected by a [BufferSubscriber].
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct StreamConfig {
+ /// Width in pixels of streaming buffers.
+ pub width: u32,
+ /// Height in pixels of streaming buffers.
+ pub height: u32,
+ /// Number of layers of streaming buffers.
+ pub layers: u32,
+ /// Format of streaming buffers.
+ pub format: AHardwareBuffer_Format::Type,
+ /// Usage of streaming buffers.
+ pub usage: AHardwareBuffer_UsageFlags,
+ /// Stride of streaming buffers.
+ pub stride: u32,
+}
+
+impl StreamConfig {
+ /// Tries to create a new HardwareBuffer from settings in a [StreamConfig].
+ pub fn create_hardware_buffer(&self) -> Option<HardwareBuffer> {
+ HardwareBuffer::new(self.width, self.height, self.layers, self.format, self.usage)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_create_hardware_buffer() {
+ let config = StreamConfig {
+ width: 123,
+ height: 456,
+ layers: 1,
+ format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN
+ | AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
+ stride: 0,
+ };
+
+ let maybe_buffer = config.create_hardware_buffer();
+ assert!(maybe_buffer.is_some());
+
+ let buffer = maybe_buffer.unwrap();
+ assert_eq!(config.width, buffer.width());
+ assert_eq!(config.height, buffer.height());
+ assert_eq!(config.format, buffer.format());
+ assert_eq!(config.usage, buffer.usage());
+ }
+}
diff --git a/libs/bufferstreams/rust/src/subscribers/mod.rs b/libs/bufferstreams/rust/src/subscribers/mod.rs
new file mode 100644
index 0000000000..dd038c6c32
--- /dev/null
+++ b/libs/bufferstreams/rust/src/subscribers/mod.rs
@@ -0,0 +1,20 @@
+// Copyright (C) 2023 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.
+
+//! This module provides [BufferSubscriber] implementations and helpers.
+
+mod shared;
+pub mod testing;
+
+pub use shared::*;
diff --git a/libs/bufferstreams/rust/src/subscribers/shared.rs b/libs/bufferstreams/rust/src/subscribers/shared.rs
new file mode 100644
index 0000000000..46c58dc04a
--- /dev/null
+++ b/libs/bufferstreams/rust/src/subscribers/shared.rs
@@ -0,0 +1,94 @@
+// Copyright (C) 2023 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.
+
+//! This module provides [BufferSubscriber] implementations and helpers.
+
+use std::sync::{Arc, Mutex};
+
+use crate::*;
+
+/// A [BufferSubscriber] wrapper that provides shared access.
+///
+/// Normally, [BufferSubscriber]s are fully owned by the publisher that they are attached to. With
+/// [SharedSubscriber], a
+///
+/// # Panics
+///
+/// [BufferSubscriber::on_subscribe] on a [SharedSubscriber] can only be called once, otherwise it
+/// will panic. This is to prevent accidental and unsupported sharing between multiple publishers to
+/// reflect the usual behavior where a publisher takes full ownership of a subscriber.
+pub struct SharedSubscriber<S: BufferSubscriber>(Arc<Mutex<SharedSubscriberInner<S>>>);
+
+struct SharedSubscriberInner<S: BufferSubscriber> {
+ subscriber: S,
+ is_subscribed: bool,
+}
+
+impl<S: BufferSubscriber> SharedSubscriber<S> {
+ /// Create a new wrapper around a [BufferSubscriber].
+ pub fn new(subscriber: S) -> Self {
+ Self(Arc::new(Mutex::new(SharedSubscriberInner { subscriber, is_subscribed: false })))
+ }
+
+ /// Provides access to an immutable reference to the wrapped [BufferSubscriber].
+ pub fn map_inner<R, F: FnOnce(&S) -> R>(&self, f: F) -> R {
+ let inner = self.0.lock().unwrap();
+ f(&inner.subscriber)
+ }
+
+ /// Provides access to a mutable reference to the wrapped [BufferSubscriber].
+ pub fn map_inner_mut<R, F: FnOnce(&mut S) -> R>(&self, f: F) -> R {
+ let mut inner = self.0.lock().unwrap();
+ f(&mut inner.subscriber)
+ }
+}
+
+impl<S: BufferSubscriber> Clone for SharedSubscriber<S> {
+ fn clone(&self) -> Self {
+ Self(Arc::clone(&self.0))
+ }
+}
+
+impl<S: BufferSubscriber> BufferSubscriber for SharedSubscriber<S> {
+ fn get_subscriber_stream_config(&self) -> StreamConfig {
+ let inner = self.0.lock().unwrap();
+ inner.subscriber.get_subscriber_stream_config()
+ }
+
+ fn on_subscribe(&mut self, subscription: Box<dyn BufferSubscription>) {
+ let mut inner = self.0.lock().unwrap();
+ assert!(
+ !inner.is_subscribed,
+ "A SharedSubscriber can not be shared between two BufferPublishers"
+ );
+ inner.is_subscribed = true;
+
+ inner.subscriber.on_subscribe(subscription);
+ }
+
+ fn on_next(&mut self, frame: Frame) {
+ let mut inner = self.0.lock().unwrap();
+ inner.subscriber.on_next(frame);
+ }
+
+ fn on_error(&mut self, error: BufferError) {
+ let mut inner = self.0.lock().unwrap();
+ inner.subscriber.on_error(error);
+ }
+
+ fn on_complete(&mut self) {
+ let mut inner = self.0.lock().unwrap();
+ inner.subscriber.on_complete();
+ }
+}
diff --git a/libs/bufferstreams/rust/src/subscribers/testing.rs b/libs/bufferstreams/rust/src/subscribers/testing.rs
new file mode 100644
index 0000000000..b7e970579e
--- /dev/null
+++ b/libs/bufferstreams/rust/src/subscribers/testing.rs
@@ -0,0 +1,106 @@
+// Copyright (C) 2023 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.
+
+//! Provides useful subscribers for testing specifically. These should not be used in normal code.
+
+use crate::*;
+
+/// Represents a callback called by a [BufferPublisher] on a [BufferSubscriber].
+pub enum TestingSubscriberEvent {
+ /// Represents a call to [BufferSubscriber::on_subscribe].
+ Subscribe,
+ /// Represents a call to [BufferSubscriber::on_next].
+ Next(Frame),
+ /// Represents a call to [BufferSubscriber::on_error].
+ Error(BufferError),
+ /// Represents a call to [BufferSubscriber::on_complete].
+ Complete,
+}
+
+/// A [BufferSubscriber] specifically for testing. Logs events as they happen which can be retrieved
+/// by the test to ensure appropriate behavior.
+pub struct TestSubscriber {
+ config: StreamConfig,
+ subscription: Option<Box<dyn BufferSubscription>>,
+ events: Vec<TestingSubscriberEvent>,
+}
+
+impl TestSubscriber {
+ /// Create a new [TestSubscriber].
+ pub fn new(config: StreamConfig) -> Self {
+ Self { config, subscription: None, events: Vec::new() }
+ }
+
+ /// Returns true if this [BufferSubscriber] has an active subscription.
+ pub fn has_subscription(&self) -> bool {
+ self.subscription.is_some()
+ }
+
+ /// Make a request on behalf of this test subscriber.
+ ///
+ /// This will panic if there is no owned subscription.
+ pub fn request(&self, n: u64) {
+ let subscription = self
+ .subscription
+ .as_deref()
+ .expect("Tried to request on a TestSubscriber with no subscription");
+ subscription.request(n);
+ }
+
+ /// Cancel on behalf of this test subscriber.
+ ///
+ /// # Panics
+ ///
+ /// This will panic if there is no owned subscription.
+ pub fn cancel(&self) {
+ let subscription = self
+ .subscription
+ .as_deref()
+ .expect("Tried to cancel a TestSubscriber with no subscription");
+ subscription.cancel();
+ }
+
+ /// Gets all of the events that have happened to this [BufferSubscriber] since the last call
+ /// to this function or it was created.
+ pub fn take_events(&mut self) -> Vec<TestingSubscriberEvent> {
+ let mut out = Vec::new();
+ out.append(&mut self.events);
+ out
+ }
+}
+
+impl BufferSubscriber for TestSubscriber {
+ fn get_subscriber_stream_config(&self) -> StreamConfig {
+ self.config
+ }
+
+ fn on_subscribe(&mut self, subscription: Box<dyn BufferSubscription>) {
+ assert!(self.subscription.is_none(), "TestSubscriber must only be subscribed to once");
+ self.subscription = Some(subscription);
+
+ self.events.push(TestingSubscriberEvent::Subscribe);
+ }
+
+ fn on_next(&mut self, frame: Frame) {
+ self.events.push(TestingSubscriberEvent::Next(frame));
+ }
+
+ fn on_error(&mut self, error: BufferError) {
+ self.events.push(TestingSubscriberEvent::Error(error));
+ }
+
+ fn on_complete(&mut self) {
+ self.events.push(TestingSubscriberEvent::Complete);
+ }
+}
diff --git a/libs/bufferstreams/rust/src/subscriptions/mod.rs b/libs/bufferstreams/rust/src/subscriptions/mod.rs
new file mode 100644
index 0000000000..e046dbbda3
--- /dev/null
+++ b/libs/bufferstreams/rust/src/subscriptions/mod.rs
@@ -0,0 +1,19 @@
+// Copyright (C) 2023 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.
+
+//! This module provides [BufferSubscription] implementations and helpers.
+
+mod shared_buffer_subscription;
+
+pub use shared_buffer_subscription::*;
diff --git a/libs/bufferstreams/rust/src/subscriptions/shared_buffer_subscription.rs b/libs/bufferstreams/rust/src/subscriptions/shared_buffer_subscription.rs
new file mode 100644
index 0000000000..90275c7320
--- /dev/null
+++ b/libs/bufferstreams/rust/src/subscriptions/shared_buffer_subscription.rs
@@ -0,0 +1,84 @@
+// Copyright (C) 2023 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.
+
+use std::sync::{Arc, Mutex};
+
+use crate::*;
+
+/// A simple sharable helper that can be used as a [BufferSubscription] by a [BufferSubscriber] and
+/// as a state tracker by a [BufferPublisher].
+#[derive(Clone, Debug)]
+pub struct SharedBufferSubscription(Arc<Mutex<BufferSubscriptionData>>);
+
+#[derive(Debug, Default)]
+struct BufferSubscriptionData {
+ requests: u64,
+ is_cancelled: bool,
+}
+
+impl SharedBufferSubscription {
+ /// Create a new [SharedBufferSubscription].
+ pub fn new() -> Self {
+ SharedBufferSubscription::default()
+ }
+
+ /// Clone this [SharedBufferSubscription] so it can be passed into
+ /// [BufferSubscriber::on_subscribe].
+ pub fn clone_for_subscriber(&self) -> Box<dyn BufferSubscription> {
+ Box::new(self.clone()) as Box<dyn BufferSubscription>
+ }
+
+ /// If possible (not cancelled and with requests pending), take
+ pub fn take_request(&self) -> bool {
+ let mut data = self.0.lock().unwrap();
+
+ if data.is_cancelled || data.requests == 0 {
+ false
+ } else {
+ data.requests -= 1;
+ true
+ }
+ }
+
+ /// Get the number of pending requests made by the [BufferSubscriber] via
+ /// [BufferSubscription::request].
+ pub fn pending_requests(&self) -> u64 {
+ self.0.lock().unwrap().requests
+ }
+
+ /// Get get whether the [BufferSubscriber] has called [BufferSubscription::cancel].
+ pub fn is_cancelled(&self) -> bool {
+ self.0.lock().unwrap().is_cancelled
+ }
+}
+
+impl Default for SharedBufferSubscription {
+ fn default() -> Self {
+ Self(Arc::new(Mutex::new(BufferSubscriptionData::default())))
+ }
+}
+
+impl BufferSubscription for SharedBufferSubscription {
+ fn request(&self, n: u64) {
+ let mut data = self.0.lock().unwrap();
+ if !data.is_cancelled {
+ data.requests = data.requests.saturating_add(n);
+ }
+ }
+
+ fn cancel(&self) {
+ let mut data = self.0.lock().unwrap();
+ data.is_cancelled = true;
+ }
+}
diff --git a/libs/bufferstreams/update_include.sh b/libs/bufferstreams/update_include.sh
new file mode 100755
index 0000000000..e986e9fb08
--- /dev/null
+++ b/libs/bufferstreams/update_include.sh
@@ -0,0 +1,2 @@
+cd rust
+cbindgen --config cbindgen.toml --crate bufferstreams --output ../include/bufferstreams.h
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp
index ea1b5e4998..918680d6a7 100644
--- a/libs/ftl/Android.bp
+++ b/libs/ftl/Android.bp
@@ -17,6 +17,7 @@ cc_test {
"enum_test.cpp",
"fake_guard_test.cpp",
"flags_test.cpp",
+ "function_test.cpp",
"future_test.cpp",
"match_test.cpp",
"mixins_test.cpp",
diff --git a/libs/ftl/enum_test.cpp b/libs/ftl/enum_test.cpp
index 5592a01fde..b68c2c3d02 100644
--- a/libs/ftl/enum_test.cpp
+++ b/libs/ftl/enum_test.cpp
@@ -33,6 +33,11 @@ static_assert(ftl::enum_name<E::ftl_last>() == "F");
static_assert(ftl::enum_name(E::C).value_or("?") == "C");
static_assert(ftl::enum_name(E{3}).value_or("?") == "?");
+static_assert(ftl::enum_name_full<E::B>() == "E::B");
+static_assert(ftl::enum_name_full<E::ftl_last>() == "E::F");
+static_assert(ftl::enum_name_full(E::C).value_or("?") == "E::C");
+static_assert(ftl::enum_name_full(E{3}).value_or("?") == "?");
+
enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 };
static_assert(ftl::enum_begin_v<F> == F{0});
@@ -60,6 +65,10 @@ static_assert(ftl::enum_name<Flags::kNone>() == "kNone");
static_assert(ftl::enum_name<Flags::kFlag4>() == "kFlag4");
static_assert(ftl::enum_name<Flags::kFlag7>() == "kFlag7");
+static_assert(ftl::enum_name_full<Flags::kNone>() == "Flags::kNone");
+static_assert(ftl::enum_name_full<Flags::kFlag4>() == "Flags::kFlag4");
+static_assert(ftl::enum_name_full<Flags::kFlag7>() == "Flags::kFlag7");
+
// Though not flags, the enumerators are within the implicit range of bit indices.
enum class Planet : std::uint8_t {
kMercury,
@@ -81,6 +90,9 @@ static_assert(ftl::enum_size_v<Planet> == 8);
static_assert(ftl::enum_name<Planet::kMercury>() == "kMercury");
static_assert(ftl::enum_name<Planet::kSaturn>() == "kSaturn");
+static_assert(ftl::enum_name_full<Planet::kMercury>() == "Planet::kMercury");
+static_assert(ftl::enum_name_full<Planet::kSaturn>() == "Planet::kSaturn");
+
// Unscoped enum must define explicit range, even if the underlying type is fixed.
enum Temperature : int {
kRoom = 20,
@@ -122,16 +134,28 @@ TEST(Enum, Name) {
EXPECT_EQ(ftl::enum_name(Planet::kEarth), "kEarth");
EXPECT_EQ(ftl::enum_name(Planet::kNeptune), "kNeptune");
+ EXPECT_EQ(ftl::enum_name_full(Planet::kEarth), "Planet::kEarth");
+ EXPECT_EQ(ftl::enum_name_full(Planet::kNeptune), "Planet::kNeptune");
+
EXPECT_EQ(ftl::enum_name(kPluto), std::nullopt);
+ EXPECT_EQ(ftl::enum_name_full(kPluto), std::nullopt);
}
{
EXPECT_EQ(ftl::enum_name(kRoom), "kRoom");
EXPECT_EQ(ftl::enum_name(kFridge), "kFridge");
EXPECT_EQ(ftl::enum_name(kFreezer), "kFreezer");
+ EXPECT_EQ(ftl::enum_name(kRoom), "kRoom");
+ EXPECT_EQ(ftl::enum_name(kFridge), "kFridge");
+ EXPECT_EQ(ftl::enum_name(kFreezer), "kFreezer");
+
EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(-30)), std::nullopt);
EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(0)), std::nullopt);
EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(100)), std::nullopt);
+
+ EXPECT_EQ(ftl::enum_name_full(static_cast<Temperature>(-30)), std::nullopt);
+ EXPECT_EQ(ftl::enum_name_full(static_cast<Temperature>(0)), std::nullopt);
+ EXPECT_EQ(ftl::enum_name_full(static_cast<Temperature>(100)), std::nullopt);
}
}
@@ -158,16 +182,30 @@ TEST(Enum, String) {
EXPECT_EQ(ftl::enum_string(Planet::kEarth), "kEarth");
EXPECT_EQ(ftl::enum_string(Planet::kNeptune), "kNeptune");
+ EXPECT_EQ(ftl::enum_string_full(Planet::kEarth), "Planet::kEarth");
+ EXPECT_EQ(ftl::enum_string_full(Planet::kNeptune), "Planet::kNeptune");
+
EXPECT_EQ(ftl::enum_string(kPluto), "8");
+
+ EXPECT_EQ(ftl::enum_string_full(kPluto), "8");
+
}
{
EXPECT_EQ(ftl::enum_string(kRoom), "kRoom");
EXPECT_EQ(ftl::enum_string(kFridge), "kFridge");
EXPECT_EQ(ftl::enum_string(kFreezer), "kFreezer");
+ EXPECT_EQ(ftl::enum_string_full(kRoom), "20");
+ EXPECT_EQ(ftl::enum_string_full(kFridge), "4");
+ EXPECT_EQ(ftl::enum_string_full(kFreezer), "-18");
+
EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(-30)), "-30");
EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(0)), "0");
EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(100)), "100");
+
+ EXPECT_EQ(ftl::enum_string_full(static_cast<Temperature>(-30)), "-30");
+ EXPECT_EQ(ftl::enum_string_full(static_cast<Temperature>(0)), "0");
+ EXPECT_EQ(ftl::enum_string_full(static_cast<Temperature>(100)), "100");
}
}
diff --git a/libs/ftl/function_test.cpp b/libs/ftl/function_test.cpp
new file mode 100644
index 0000000000..91b5e08041
--- /dev/null
+++ b/libs/ftl/function_test.cpp
@@ -0,0 +1,379 @@
+/*
+ * Copyright 2022 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.
+ */
+
+#include <ftl/function.h>
+#include <gtest/gtest.h>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <string_view>
+#include <type_traits>
+
+namespace android::test {
+namespace {
+
+// Create an alias to composite requirements defined by the trait class `T` for easier testing.
+template <typename T, typename S>
+inline constexpr bool is_opaquely_storable = (T::template require_trivially_copyable<S> &&
+ T::template require_trivially_destructible<S> &&
+ T::template require_will_fit_in_opaque_storage<S> &&
+ T::template require_alignment_compatible<S>);
+
+// `I` gives a count of sizeof(std::intptr_t) bytes , and `J` gives a raw count of bytes
+template <size_t I, size_t J = 0>
+struct KnownSizeFunctionObject {
+ using Data = std::array<std::byte, sizeof(std::intptr_t) * I + J>;
+ void operator()() const {};
+ Data data{};
+};
+
+} // namespace
+
+// static_assert the expected type traits
+static_assert(std::is_invocable_r_v<void, ftl::Function<void()>>);
+static_assert(std::is_trivially_copyable_v<ftl::Function<void()>>);
+static_assert(std::is_trivially_destructible_v<ftl::Function<void()>>);
+static_assert(std::is_trivially_copy_constructible_v<ftl::Function<void()>>);
+static_assert(std::is_trivially_move_constructible_v<ftl::Function<void()>>);
+static_assert(std::is_trivially_copy_assignable_v<ftl::Function<void()>>);
+static_assert(std::is_trivially_move_assignable_v<ftl::Function<void()>>);
+
+template <typename T>
+using function_traits = ftl::details::function_traits<T>;
+
+// static_assert that the expected value of N is used for known function object sizes.
+static_assert(function_traits<KnownSizeFunctionObject<0, 0>>::size == 0);
+static_assert(function_traits<KnownSizeFunctionObject<0, 1>>::size == 0);
+static_assert(function_traits<KnownSizeFunctionObject<1, 0>>::size == 0);
+static_assert(function_traits<KnownSizeFunctionObject<1, 1>>::size == 1);
+static_assert(function_traits<KnownSizeFunctionObject<2, 0>>::size == 1);
+static_assert(function_traits<KnownSizeFunctionObject<2, 1>>::size == 2);
+
+// Check that is_function_v works
+static_assert(!ftl::is_function_v<KnownSizeFunctionObject<0>>);
+static_assert(!ftl::is_function_v<std::function<void()>>);
+static_assert(ftl::is_function_v<ftl::Function<void()>>);
+
+// static_assert what can and cannot be stored inside the opaque storage
+
+template <size_t N>
+using function_opaque_storage = ftl::details::function_opaque_storage<N>;
+
+// Function objects can be stored if they fit.
+static_assert(is_opaquely_storable<function_opaque_storage<0>, KnownSizeFunctionObject<0>>);
+static_assert(is_opaquely_storable<function_opaque_storage<0>, KnownSizeFunctionObject<1>>);
+static_assert(!is_opaquely_storable<function_opaque_storage<0>, KnownSizeFunctionObject<2>>);
+
+static_assert(is_opaquely_storable<function_opaque_storage<1>, KnownSizeFunctionObject<2>>);
+static_assert(!is_opaquely_storable<function_opaque_storage<1>, KnownSizeFunctionObject<3>>);
+
+static_assert(is_opaquely_storable<function_opaque_storage<2>, KnownSizeFunctionObject<3>>);
+static_assert(!is_opaquely_storable<function_opaque_storage<2>, KnownSizeFunctionObject<4>>);
+
+// Another opaque storage can be stored if it fits. This property is used to copy smaller
+// ftl::Functions into larger ones.
+static_assert(is_opaquely_storable<function_opaque_storage<2>, function_opaque_storage<0>::type>);
+static_assert(is_opaquely_storable<function_opaque_storage<2>, function_opaque_storage<1>::type>);
+static_assert(is_opaquely_storable<function_opaque_storage<2>, function_opaque_storage<2>::type>);
+static_assert(!is_opaquely_storable<function_opaque_storage<2>, function_opaque_storage<3>::type>);
+
+// Function objects that aren't trivially copyable or destroyable cannot be stored.
+auto lambda_capturing_unique_ptr = [ptr = std::unique_ptr<void*>()] { static_cast<void>(ptr); };
+static_assert(
+ !is_opaquely_storable<function_opaque_storage<2>, decltype(lambda_capturing_unique_ptr)>);
+
+// Keep in sync with "Example usage" in header file.
+TEST(Function, Example) {
+ using namespace std::string_view_literals;
+
+ class MyClass {
+ public:
+ void on_event() const {}
+ int on_string(int*, std::string_view) { return 1; }
+
+ auto get_function() {
+ return ftl::make_function([this] { on_event(); });
+ }
+ } cls;
+
+ // A function container with no arguments, and returning no value.
+ ftl::Function<void()> f;
+
+ // Construct a ftl::Function containing a small lambda.
+ f = cls.get_function();
+
+ // Construct a ftl::Function that calls `cls.on_event()`.
+ f = ftl::make_function<&MyClass::on_event>(&cls);
+
+ // Create a do-nothing function.
+ f = ftl::no_op;
+
+ // Invoke the contained function.
+ f();
+
+ // Also invokes it.
+ std::invoke(f);
+
+ // Create a typedef to give a more meaningful name and bound the size.
+ using MyFunction = ftl::Function<int(std::string_view), 2>;
+ int* ptr = nullptr;
+ auto f1 =
+ MyFunction::make([cls = &cls, ptr](std::string_view sv) { return cls->on_string(ptr, sv); });
+ int r = f1("abc"sv);
+
+ // Returns a default-constructed int (0).
+ f1 = ftl::no_op;
+ r = f1("abc"sv);
+ EXPECT_EQ(r, 0);
+}
+
+TEST(Function, BasicOperations) {
+ // Default constructible.
+ ftl::Function<int()> f;
+
+ // Compares as empty
+ EXPECT_FALSE(f);
+ EXPECT_TRUE(f == nullptr);
+ EXPECT_FALSE(f != nullptr);
+ EXPECT_TRUE(ftl::Function<int()>() == f);
+ EXPECT_FALSE(ftl::Function<int()>() != f);
+
+ // Assigning no_op sets it to not empty.
+ f = ftl::no_op;
+
+ // Verify it can be called, and that it returns a default constructed value.
+ EXPECT_EQ(f(), 0);
+
+ // Comparable when non-empty.
+ EXPECT_TRUE(f);
+ EXPECT_FALSE(f == nullptr);
+ EXPECT_TRUE(f != nullptr);
+ EXPECT_FALSE(ftl::Function<int()>() == f);
+ EXPECT_TRUE(ftl::Function<int()>() != f);
+
+ // Constructing from nullptr means empty.
+ f = ftl::Function<int()>{nullptr};
+ EXPECT_FALSE(f);
+
+ // Assigning nullptr means it is empty.
+ f = nullptr;
+ EXPECT_FALSE(f);
+
+ // Move construction
+ f = ftl::no_op;
+ ftl::Function<int()> g{std::move(f)};
+ EXPECT_TRUE(g != nullptr);
+
+ // Move assignment
+ f = nullptr;
+ f = std::move(g);
+ EXPECT_TRUE(f != nullptr);
+
+ // Copy construction
+ ftl::Function<int()> h{f};
+ EXPECT_TRUE(h != nullptr);
+
+ // Copy assignment
+ g = h;
+ EXPECT_TRUE(g != nullptr);
+}
+
+TEST(Function, CanMoveConstructFromLambda) {
+ auto lambda = [] {};
+ ftl::Function<void()> f{std::move(lambda)};
+}
+
+TEST(Function, TerseDeducedConstructAndAssignFromLambda) {
+ auto f = ftl::Function([] { return 1; });
+ EXPECT_EQ(f(), 1);
+
+ f = [] { return 2; };
+ EXPECT_EQ(f(), 2);
+}
+
+namespace {
+
+struct ImplicitConversionsHelper {
+ auto exact(int) -> int { return 0; }
+ auto inexact(long) -> short { return 0; }
+ // TODO: Switch to `auto templated(auto x)` with C++20
+ template <typename T>
+ T templated(T x) {
+ return x;
+ }
+
+ static auto static_exact(int) -> int { return 0; }
+ static auto static_inexact(long) -> short { return 0; }
+ // TODO: Switch to `static auto static_templated(auto x)` with C++20
+ template <typename T>
+ static T static_templated(T x) {
+ return x;
+ }
+};
+
+} // namespace
+
+TEST(Function, ImplicitConversions) {
+ using Function = ftl::Function<int(int)>;
+ auto check = [](Function f) { return f(0); };
+ auto exact = [](int) -> int { return 0; };
+ auto inexact = [](long) -> short { return 0; };
+ auto templated = [](auto x) { return x; };
+
+ ImplicitConversionsHelper helper;
+
+ // Note, `check(nullptr)` would crash, so we can only check if it would be invocable.
+ static_assert(std::is_invocable_v<decltype(check), decltype(nullptr)>);
+
+ // Note: We invoke each of these to fully expand all the templates involved.
+ EXPECT_EQ(check(ftl::no_op), 0);
+
+ EXPECT_EQ(check(exact), 0);
+ EXPECT_EQ(check(inexact), 0);
+ EXPECT_EQ(check(templated), 0);
+
+ EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::exact>(&helper)), 0);
+ EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::inexact>(&helper)), 0);
+ EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::templated<int>>(&helper)), 0);
+
+ EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::static_exact>()), 0);
+ EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::static_inexact>()), 0);
+ EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::static_templated<int>>()), 0);
+}
+
+TEST(Function, MakeWithNonConstMemberFunction) {
+ struct Observer {
+ bool called = false;
+ void setCalled() { called = true; }
+ } observer;
+
+ auto f = ftl::make_function<&Observer::setCalled>(&observer);
+
+ f();
+
+ EXPECT_TRUE(observer.called);
+
+ EXPECT_TRUE(f == ftl::Function<void()>::make<&Observer::setCalled>(&observer));
+}
+
+TEST(Function, MakeWithConstMemberFunction) {
+ struct Observer {
+ mutable bool called = false;
+ void setCalled() const { called = true; }
+ } observer;
+
+ const auto f = ftl::make_function<&Observer::setCalled>(&observer);
+
+ f();
+
+ EXPECT_TRUE(observer.called);
+
+ EXPECT_TRUE(f == ftl::Function<void()>::make<&Observer::setCalled>(&observer));
+}
+
+TEST(Function, MakeWithConstClassPointer) {
+ const struct Observer {
+ mutable bool called = false;
+ void setCalled() const { called = true; }
+ } observer;
+
+ const auto f = ftl::make_function<&Observer::setCalled>(&observer);
+
+ f();
+
+ EXPECT_TRUE(observer.called);
+
+ EXPECT_TRUE(f == ftl::Function<void()>::make<&Observer::setCalled>(&observer));
+}
+
+TEST(Function, MakeWithNonCapturingLambda) {
+ auto f = ftl::make_function([](int a, int b) { return a + b; });
+ EXPECT_EQ(f(1, 2), 3);
+}
+
+TEST(Function, MakeWithCapturingLambda) {
+ bool called = false;
+ auto f = ftl::make_function([&called](int a, int b) {
+ called = true;
+ return a + b;
+ });
+ EXPECT_EQ(f(1, 2), 3);
+ EXPECT_TRUE(called);
+}
+
+TEST(Function, MakeWithCapturingMutableLambda) {
+ bool called = false;
+ auto f = ftl::make_function([&called](int a, int b) mutable {
+ called = true;
+ return a + b;
+ });
+ EXPECT_EQ(f(1, 2), 3);
+ EXPECT_TRUE(called);
+}
+
+TEST(Function, MakeWithThreePointerCapturingLambda) {
+ bool my_bool = false;
+ int my_int = 0;
+ float my_float = 0.f;
+
+ auto f = ftl::make_function(
+ [ptr_bool = &my_bool, ptr_int = &my_int, ptr_float = &my_float](int a, int b) mutable {
+ *ptr_bool = true;
+ *ptr_int = 1;
+ *ptr_float = 1.f;
+
+ return a + b;
+ });
+
+ EXPECT_EQ(f(1, 2), 3);
+
+ EXPECT_TRUE(my_bool);
+ EXPECT_EQ(my_int, 1);
+ EXPECT_EQ(my_float, 1.f);
+}
+
+TEST(Function, MakeWithFreeFunction) {
+ auto f = ftl::make_function<&std::make_unique<int, int>>();
+ std::unique_ptr<int> unique_int = f(1);
+ ASSERT_TRUE(unique_int);
+ EXPECT_EQ(*unique_int, 1);
+}
+
+TEST(Function, CopyToLarger) {
+ int counter = 0;
+ ftl::Function<void()> a{[ptr_counter = &counter] { (*ptr_counter)++; }};
+ ftl::Function<void(), 1> b = a;
+ ftl::Function<void(), 2> c = a;
+
+ EXPECT_EQ(counter, 0);
+ a();
+ EXPECT_EQ(counter, 1);
+ b();
+ EXPECT_EQ(counter, 2);
+ c();
+ EXPECT_EQ(counter, 3);
+
+ b = [ptr_counter = &counter] { (*ptr_counter) += 2; };
+ c = [ptr_counter = &counter] { (*ptr_counter) += 3; };
+
+ b();
+ EXPECT_EQ(counter, 5);
+ c();
+ EXPECT_EQ(counter, 8);
+}
+
+} // namespace android::test
diff --git a/libs/gralloc/types/Android.bp b/libs/gralloc/types/Android.bp
index 6d1dfe8124..aab1276b47 100644
--- a/libs/gralloc/types/Android.bp
+++ b/libs/gralloc/types/Android.bp
@@ -58,7 +58,7 @@ cc_library {
],
export_shared_lib_headers: [
- "android.hardware.graphics.common-V4-ndk",
+ "android.hardware.graphics.common-V5-ndk",
"android.hardware.graphics.mapper@4.0",
"libhidlbase",
],
diff --git a/libs/gralloc/types/Gralloc4.cpp b/libs/gralloc/types/Gralloc4.cpp
index 61e6657621..ce35906949 100644
--- a/libs/gralloc/types/Gralloc4.cpp
+++ b/libs/gralloc/types/Gralloc4.cpp
@@ -1307,6 +1307,10 @@ std::string getChromaSitingName(const ExtendableType& chromaSiting) {
return "SitedInterstitial";
case ChromaSiting::COSITED_HORIZONTAL:
return "CositedHorizontal";
+ case ChromaSiting::COSITED_VERTICAL:
+ return "CositedVertical";
+ case ChromaSiting::COSITED_BOTH:
+ return "CositedBoth";
}
}
diff --git a/libs/graphicsenv/IGpuService.cpp b/libs/graphicsenv/IGpuService.cpp
index 4c070aec01..5dc195c438 100644
--- a/libs/graphicsenv/IGpuService.cpp
+++ b/libs/graphicsenv/IGpuService.cpp
@@ -64,9 +64,17 @@ public:
void setTargetStatsArray(const std::string& appPackageName, const uint64_t driverVersionCode,
const GpuStatsInfo::Stats stats, const uint64_t* values,
const uint32_t valueCount) override {
- for (uint32_t i = 0; i < valueCount; i++) {
- setTargetStats(appPackageName, driverVersionCode, stats, values[i]);
- }
+ Parcel data, reply;
+ data.writeInterfaceToken(IGpuService::getInterfaceDescriptor());
+
+ data.writeUtf8AsUtf16(appPackageName);
+ data.writeUint64(driverVersionCode);
+ data.writeInt32(static_cast<int32_t>(stats));
+ data.writeUint32(valueCount);
+ data.write(values, valueCount * sizeof(uint64_t));
+
+ remote()->transact(BnGpuService::SET_TARGET_STATS_ARRAY, data, &reply,
+ IBinder::FLAG_ONEWAY);
}
void setUpdatableDriverPath(const std::string& driverPath) override {
@@ -164,6 +172,31 @@ status_t BnGpuService::onTransact(uint32_t code, const Parcel& data, Parcel* rep
return OK;
}
+ case SET_TARGET_STATS_ARRAY: {
+ CHECK_INTERFACE(IGpuService, data, reply);
+
+ std::string appPackageName;
+ if ((status = data.readUtf8FromUtf16(&appPackageName)) != OK) return status;
+
+ uint64_t driverVersionCode;
+ if ((status = data.readUint64(&driverVersionCode)) != OK) return status;
+
+ int32_t stats;
+ if ((status = data.readInt32(&stats)) != OK) return status;
+
+ uint32_t valueCount;
+ if ((status = data.readUint32(&valueCount)) != OK) return status;
+
+ std::vector<uint64_t> values(valueCount);
+ if ((status = data.read(values.data(), valueCount * sizeof(uint64_t))) != OK) {
+ return status;
+ }
+
+ setTargetStatsArray(appPackageName, driverVersionCode,
+ static_cast<GpuStatsInfo::Stats>(stats), values.data(), valueCount);
+
+ return OK;
+ }
case SET_UPDATABLE_DRIVER_PATH: {
CHECK_INTERFACE(IGpuService, data, reply);
@@ -180,9 +213,9 @@ status_t BnGpuService::onTransact(uint32_t code, const Parcel& data, Parcel* rep
return reply->writeUtf8AsUtf16(driverPath);
}
case SHELL_COMMAND_TRANSACTION: {
- int in = data.readFileDescriptor();
- int out = data.readFileDescriptor();
- int err = data.readFileDescriptor();
+ int in = dup(data.readFileDescriptor());
+ int out = dup(data.readFileDescriptor());
+ int err = dup(data.readFileDescriptor());
std::vector<String16> args;
data.readString16Vector(&args);
@@ -195,6 +228,9 @@ status_t BnGpuService::onTransact(uint32_t code, const Parcel& data, Parcel* rep
status = shellCommand(in, out, err, args);
if (resultReceiver != nullptr) resultReceiver->send(status);
+ ::close(in);
+ ::close(out);
+ ::close(err);
return OK;
}
diff --git a/libs/graphicsenv/include/graphicsenv/IGpuService.h b/libs/graphicsenv/include/graphicsenv/IGpuService.h
index e3857d2ec0..45f05d6555 100644
--- a/libs/graphicsenv/include/graphicsenv/IGpuService.h
+++ b/libs/graphicsenv/include/graphicsenv/IGpuService.h
@@ -63,6 +63,7 @@ public:
SET_UPDATABLE_DRIVER_PATH,
GET_UPDATABLE_DRIVER_PATH,
TOGGLE_ANGLE_AS_SYSTEM_DRIVER,
+ SET_TARGET_STATS_ARRAY,
// Always append new enum to the end.
};
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 661a017f66..eb4d3df21d 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -20,6 +20,25 @@ package {
default_applicable_licenses: ["frameworks_native_license"],
}
+aconfig_declarations {
+ name: "libgui_flags",
+ package: "com.android.graphics.libgui.flags",
+ srcs: ["libgui_flags.aconfig"],
+}
+
+cc_aconfig_library {
+ name: "libguiflags",
+ host_supported: true,
+ vendor_available: true,
+ min_sdk_version: "29",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media.swcodec",
+ "test_com.android.media.swcodec",
+ ],
+ aconfig_declarations: "libgui_flags",
+}
+
cc_library_headers {
name: "libgui_headers",
vendor_available: true,
@@ -36,6 +55,8 @@ cc_library_headers {
"android.hardware.graphics.bufferqueue@1.0",
"android.hardware.graphics.bufferqueue@2.0",
],
+ static_libs: ["libguiflags"],
+ export_static_lib_headers: ["libguiflags"],
min_sdk_version: "29",
// TODO(b/218719284) can media use be constrained to libgui_bufferqueue_static?
apex_available: [
@@ -192,26 +213,8 @@ cc_library_static {
},
}
-cc_library_shared {
- name: "libgui",
- vendor_available: true,
- vndk: {
- enabled: true,
- private: true,
- },
- double_loadable: true,
-
- defaults: ["libgui_bufferqueue-defaults"],
-
- static_libs: [
- "libgui_aidl_static",
- "libgui_window_info_static",
- ],
- export_static_lib_headers: [
- "libgui_aidl_static",
- "libgui_window_info_static",
- ],
-
+filegroup {
+ name: "libgui-sources",
srcs: [
":framework_native_aidl_binder",
":framework_native_aidl_gui",
@@ -255,11 +258,43 @@ cc_library_shared {
"bufferqueue/2.0/B2HProducerListener.cpp",
"bufferqueue/2.0/H2BGraphicBufferProducer.cpp",
],
+}
+cc_defaults {
+ name: "libgui-defaults",
+ defaults: ["libgui_bufferqueue-defaults"],
+ srcs: [":libgui-sources"],
+ static_libs: [
+ "libgui_aidl_static",
+ "libgui_window_info_static",
+ "libguiflags",
+ ],
shared_libs: [
"libbinder",
"libGLESv2",
],
+ export_static_lib_headers: [
+ "libguiflags",
+ ],
+}
+
+cc_library_shared {
+ name: "libgui",
+ vendor_available: true,
+ vndk: {
+ enabled: true,
+ private: true,
+ },
+ double_loadable: true,
+
+ defaults: [
+ "libgui-defaults",
+ ],
+
+ export_static_lib_headers: [
+ "libgui_aidl_static",
+ "libgui_window_info_static",
+ ],
export_shared_lib_headers: [
"libbinder",
@@ -333,6 +368,7 @@ filegroup {
"BufferQueueProducer.cpp",
"BufferQueueThreadState.cpp",
"BufferSlot.cpp",
+ "FrameRateUtils.cpp",
"FrameTimestamps.cpp",
"GLConsumerUtils.cpp",
"HdrMetadata.cpp",
@@ -434,6 +470,7 @@ cc_library_static {
static_libs: [
"libgtest",
"libgmock",
+ "libguiflags",
],
srcs: [
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 207fa4fd31..f317a2eea0 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -26,6 +26,8 @@
#include <gui/BufferQueueConsumer.h>
#include <gui/BufferQueueCore.h>
#include <gui/BufferQueueProducer.h>
+
+#include <gui/FrameRateUtils.h>
#include <gui/GLConsumer.h>
#include <gui/IProducerListener.h>
#include <gui/Surface.h>
@@ -39,6 +41,9 @@
#include <android-base/thread_annotations.h>
#include <chrono>
+#include <com_android_graphics_libgui_flags.h>
+
+using namespace com::android::graphics::libgui;
using namespace std::chrono_literals;
namespace {
@@ -99,12 +104,11 @@ void BLASTBufferItemConsumer::addAndGetFrameTimestamps(const NewFrameEventsEntry
}
}
-void BLASTBufferItemConsumer::updateFrameTimestamps(uint64_t frameNumber, nsecs_t refreshStartTime,
- const sp<Fence>& glDoneFence,
- const sp<Fence>& presentFence,
- const sp<Fence>& prevReleaseFence,
- CompositorTiming compositorTiming,
- nsecs_t latchTime, nsecs_t dequeueReadyTime) {
+void BLASTBufferItemConsumer::updateFrameTimestamps(
+ uint64_t frameNumber, uint64_t previousFrameNumber, nsecs_t refreshStartTime,
+ const sp<Fence>& glDoneFence, const sp<Fence>& presentFence,
+ const sp<Fence>& prevReleaseFence, CompositorTiming compositorTiming, nsecs_t latchTime,
+ nsecs_t dequeueReadyTime) {
Mutex::Autolock lock(mMutex);
// if the producer is not connected, don't bother updating,
@@ -115,7 +119,15 @@ void BLASTBufferItemConsumer::updateFrameTimestamps(uint64_t frameNumber, nsecs_
std::shared_ptr<FenceTime> releaseFenceTime = std::make_shared<FenceTime>(prevReleaseFence);
mFrameEventHistory.addLatch(frameNumber, latchTime);
- mFrameEventHistory.addRelease(frameNumber, dequeueReadyTime, std::move(releaseFenceTime));
+ if (flags::frametimestamps_previousrelease()) {
+ if (previousFrameNumber > 0) {
+ mFrameEventHistory.addRelease(previousFrameNumber, dequeueReadyTime,
+ std::move(releaseFenceTime));
+ }
+ } else {
+ mFrameEventHistory.addRelease(frameNumber, dequeueReadyTime, std::move(releaseFenceTime));
+ }
+
mFrameEventHistory.addPreComposition(frameNumber, refreshStartTime);
mFrameEventHistory.addPostComposition(frameNumber, glDoneFenceTime, presentFenceTime,
compositorTiming);
@@ -139,6 +151,16 @@ void BLASTBufferItemConsumer::onSidebandStreamChanged() {
}
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+void BLASTBufferItemConsumer::onSetFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) {
+ sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote();
+ if (bbq != nullptr) {
+ bbq->setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+ }
+}
+#endif
+
void BLASTBufferItemConsumer::resizeFrameEventHistory(size_t newSize) {
Mutex::Autolock lock(mMutex);
mFrameEventHistory.resize(newSize);
@@ -351,6 +373,7 @@ void BLASTBufferQueue::transactionCallback(nsecs_t /*latchTime*/, const sp<Fence
if (stat.latchTime > 0) {
mBufferItemConsumer
->updateFrameTimestamps(stat.frameEventStats.frameNumber,
+ stat.frameEventStats.previousFrameNumber,
stat.frameEventStats.refreshStartTime,
stat.frameEventStats.gpuCompositionDoneFence,
stat.presentFence, stat.previousReleaseFence,
@@ -890,6 +913,10 @@ public:
status_t setFrameRate(float frameRate, int8_t compatibility,
int8_t changeFrameRateStrategy) override {
+ if (flags::bq_setframerate()) {
+ return Surface::setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+ }
+
std::lock_guard _lock{mMutex};
if (mDestroyed) {
return DEAD_OBJECT;
diff --git a/libs/gui/BufferQueue.cpp b/libs/gui/BufferQueue.cpp
index 66cad03fec..b0f6e69115 100644
--- a/libs/gui/BufferQueue.cpp
+++ b/libs/gui/BufferQueue.cpp
@@ -98,6 +98,16 @@ void BufferQueue::ProxyConsumerListener::addAndGetFrameTimestamps(
}
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+void BufferQueue::ProxyConsumerListener::onSetFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) {
+ sp<ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != nullptr) {
+ listener->onSetFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+ }
+}
+#endif
+
void BufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
sp<IGraphicBufferConsumer>* outConsumer,
bool consumerIsSurfaceFlinger) {
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index 920b83dba9..19693e37cf 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -32,6 +32,8 @@
#include <gui/BufferItem.h>
#include <gui/BufferQueueCore.h>
#include <gui/BufferQueueProducer.h>
+
+#include <gui/FrameRateUtils.h>
#include <gui/GLConsumer.h>
#include <gui/IConsumerListener.h>
#include <gui/IProducerListener.h>
@@ -1751,4 +1753,27 @@ status_t BufferQueueProducer::setAutoPrerotation(bool autoPrerotation) {
return NO_ERROR;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+status_t BufferQueueProducer::setFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) {
+ ATRACE_CALL();
+ BQ_LOGV("setFrameRate: %.2f", frameRate);
+
+ if (!ValidateFrameRate(frameRate, compatibility, changeFrameRateStrategy,
+ "BufferQueueProducer::setFrameRate")) {
+ return BAD_VALUE;
+ }
+
+ sp<IConsumerListener> listener;
+ {
+ std::lock_guard<std::mutex> lock(mCore->mMutex);
+ listener = mCore->mConsumerListener;
+ }
+ if (listener != nullptr) {
+ listener->onSetFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+ }
+ return NO_ERROR;
+}
+#endif
+
} // namespace android
diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp
index 46fb068dee..93df12471d 100644
--- a/libs/gui/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -324,6 +324,12 @@ void Choreographer::dispatchHotplug(nsecs_t, PhysicalDisplayId displayId, bool c
to_string(displayId).c_str(), toString(connected));
}
+void Choreographer::dispatchHotplugConnectionError(nsecs_t, int32_t connectionError) {
+ ALOGV("choreographer %p ~ received hotplug connection error event (connectionError=%d), "
+ "ignoring.",
+ this, connectionError);
+}
+
void Choreographer::dispatchModeChanged(nsecs_t, PhysicalDisplayId, int32_t, nsecs_t) {
LOG_ALWAYS_FATAL("dispatchModeChanged was called but was never registered");
}
@@ -394,4 +400,4 @@ int64_t Choreographer::getStartTimeNanosForVsyncId(AVsyncId vsyncId) {
return iter->second;
}
-} // namespace android \ No newline at end of file
+} // namespace android
diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp
index 8a883770d8..5dd058cf9f 100644
--- a/libs/gui/DisplayEventDispatcher.cpp
+++ b/libs/gui/DisplayEventDispatcher.cpp
@@ -173,7 +173,13 @@ bool DisplayEventDispatcher::processPendingEvents(nsecs_t* outTimestamp,
*outVsyncEventData = ev.vsync.vsyncData;
break;
case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
- dispatchHotplug(ev.header.timestamp, ev.header.displayId, ev.hotplug.connected);
+ if (ev.hotplug.connectionError == 0) {
+ dispatchHotplug(ev.header.timestamp, ev.header.displayId,
+ ev.hotplug.connected);
+ } else {
+ dispatchHotplugConnectionError(ev.header.timestamp,
+ ev.hotplug.connectionError);
+ }
break;
case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE:
dispatchModeChanged(ev.header.timestamp, ev.header.displayId,
diff --git a/libs/gui/DisplayEventReceiver.cpp b/libs/gui/DisplayEventReceiver.cpp
index 6849a95d1e..67cbc7b111 100644
--- a/libs/gui/DisplayEventReceiver.cpp
+++ b/libs/gui/DisplayEventReceiver.cpp
@@ -99,7 +99,7 @@ status_t DisplayEventReceiver::getLatestVsyncEventData(
if (mEventConnection != nullptr) {
auto status = mEventConnection->getLatestVsyncEventData(outVsyncEventData);
if (!status.isOk()) {
- ALOGE("Failed to get latest vsync event data: %s", status.exceptionMessage().c_str());
+ ALOGE("Failed to get latest vsync event data: %s", status.toString8().c_str());
return status.transactionError();
}
return NO_ERROR;
diff --git a/libs/gui/FrameRateUtils.cpp b/libs/gui/FrameRateUtils.cpp
new file mode 100644
index 0000000000..11524e2b51
--- /dev/null
+++ b/libs/gui/FrameRateUtils.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#include <gui/FrameRateUtils.h>
+#include <system/window.h>
+#include <utils/Log.h>
+
+#include <cmath>
+
+#include <com_android_graphics_libgui_flags.h>
+
+namespace android {
+using namespace com::android::graphics::libgui;
+// Returns true if the frameRate is valid.
+//
+// @param frameRate the frame rate in Hz
+// @param compatibility a ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_*
+// @param changeFrameRateStrategy a ANATIVEWINDOW_CHANGE_FRAME_RATE_*
+// @param functionName calling function or nullptr. Used for logging
+// @param privileged whether caller has unscoped surfaceflinger access
+bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy,
+ const char* inFunctionName, bool privileged) {
+ const char* functionName = inFunctionName != nullptr ? inFunctionName : "call";
+ int floatClassification = std::fpclassify(frameRate);
+ if (frameRate < 0 || floatClassification == FP_INFINITE || floatClassification == FP_NAN) {
+ ALOGE("%s failed - invalid frame rate %f", functionName, frameRate);
+ return false;
+ }
+
+ if (compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT &&
+ compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE &&
+ (!privileged ||
+ (compatibility != ANATIVEWINDOW_FRAME_RATE_EXACT &&
+ compatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE))) {
+ ALOGE("%s failed - invalid compatibility value %d privileged: %s", functionName,
+ compatibility, privileged ? "yes" : "no");
+ return false;
+ }
+
+ if (__builtin_available(android 31, *)) {
+ if (changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS &&
+ changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS) {
+ ALOGE("%s failed - invalid change frame rate strategy value %d", functionName,
+ changeFrameRateStrategy);
+ if (flags::bq_setframerate()) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+} // namespace android
diff --git a/libs/gui/FrameTimestamps.cpp b/libs/gui/FrameTimestamps.cpp
index f3eb4e83aa..afb09de94b 100644
--- a/libs/gui/FrameTimestamps.cpp
+++ b/libs/gui/FrameTimestamps.cpp
@@ -255,7 +255,6 @@ void ProducerFrameEventHistory::updateAcquireFence(
uint64_t frameNumber, std::shared_ptr<FenceTime>&& acquire) {
FrameEvents* frame = getFrame(frameNumber, &mAcquireOffset);
if (frame == nullptr) {
- ALOGE("updateAcquireFence: Did not find frame.");
return;
}
diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp
index 918ff2dd25..e81c098b85 100644
--- a/libs/gui/IGraphicBufferProducer.cpp
+++ b/libs/gui/IGraphicBufferProducer.cpp
@@ -27,11 +27,12 @@
#include <binder/Parcel.h>
#include <binder/IInterface.h>
-#include <gui/bufferqueue/1.0/H2BGraphicBufferProducer.h>
-#include <gui/bufferqueue/2.0/H2BGraphicBufferProducer.h>
#include <gui/BufferQueueDefs.h>
+
#include <gui/IGraphicBufferProducer.h>
#include <gui/IProducerListener.h>
+#include <gui/bufferqueue/1.0/H2BGraphicBufferProducer.h>
+#include <gui/bufferqueue/2.0/H2BGraphicBufferProducer.h>
namespace android {
// ----------------------------------------------------------------------------
@@ -78,6 +79,7 @@ enum {
CANCEL_BUFFERS,
QUERY_MULTIPLE,
GET_LAST_QUEUED_BUFFER2,
+ SET_FRAME_RATE,
};
class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>
@@ -761,6 +763,21 @@ public:
}
return result;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+ virtual status_t setFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) override {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+ data.writeFloat(frameRate);
+ data.writeInt32(compatibility);
+ data.writeInt32(changeFrameRateStrategy);
+ status_t result = remote()->transact(SET_FRAME_RATE, data, &reply);
+ if (result == NO_ERROR) {
+ result = reply.readInt32();
+ }
+ return result;
+ }
+#endif
};
// Out-of-line virtual method definition to trigger vtable emission in this
@@ -956,6 +973,14 @@ status_t IGraphicBufferProducer::setAutoPrerotation(bool autoPrerotation) {
return INVALID_OPERATION;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+status_t IGraphicBufferProducer::setFrameRate(float /*frameRate*/, int8_t /*compatibility*/,
+ int8_t /*changeFrameRateStrategy*/) {
+ // No-op for IGBP other than BufferQueue.
+ return INVALID_OPERATION;
+}
+#endif
+
status_t IGraphicBufferProducer::exportToParcel(Parcel* parcel) {
status_t res = OK;
res = parcel->writeUint32(USE_BUFFER_QUEUE);
@@ -1497,6 +1522,17 @@ status_t BnGraphicBufferProducer::onTransact(
reply->writeInt32(result);
return NO_ERROR;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+ case SET_FRAME_RATE: {
+ CHECK_INTERFACE(IGraphicBuffer, data, reply);
+ float frameRate = data.readFloat();
+ int8_t compatibility = data.readInt32();
+ int8_t changeFrameRateStrategy = data.readInt32();
+ status_t result = setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ }
+#endif
}
return BBinder::onTransact(code, data, reply, flags);
}
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index b526a6c92c..ff6b558d41 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -25,6 +25,7 @@
#include <gui/IGraphicBufferProducer.h>
#include <gui/ISurfaceComposer.h>
#include <gui/LayerState.h>
+#include <gui/SchedulingPolicy.h>
#include <private/gui/ParcelUtils.h>
#include <stdint.h>
#include <sys/types.h>
@@ -201,6 +202,18 @@ status_t BnSurfaceComposer::onTransact(
isAutoTimestamp, uncacheBuffers, hasListenerCallbacks,
listenerCallbacks, transactionId, mergedTransactions);
}
+ case GET_SCHEDULING_POLICY: {
+ gui::SchedulingPolicy policy;
+ const auto status = gui::getSchedulingPolicy(&policy);
+ if (!status.isOk()) {
+ return status.exceptionCode();
+ }
+
+ SAFE_PARCEL(reply->writeInt32, policy.policy);
+ SAFE_PARCEL(reply->writeInt32, policy.priority);
+ return NO_ERROR;
+ }
+
default: {
return BBinder::onTransact(code, data, reply, flags);
}
diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp
index ffe79a3a03..f5d19aac78 100644
--- a/libs/gui/ITransactionCompletedListener.cpp
+++ b/libs/gui/ITransactionCompletedListener.cpp
@@ -25,6 +25,10 @@
#include <gui/LayerState.h>
#include <private/gui/ParcelUtils.h>
+#include <com_android_graphics_libgui_flags.h>
+
+using namespace com::android::graphics::libgui;
+
namespace android {
namespace { // Anonymous
@@ -49,6 +53,11 @@ status_t FrameEventHistoryStats::writeToParcel(Parcel* output) const {
status_t err = output->writeUint64(frameNumber);
if (err != NO_ERROR) return err;
+ if (flags::frametimestamps_previousrelease()) {
+ err = output->writeUint64(previousFrameNumber);
+ if (err != NO_ERROR) return err;
+ }
+
if (gpuCompositionDoneFence) {
err = output->writeBool(true);
if (err != NO_ERROR) return err;
@@ -79,6 +88,11 @@ status_t FrameEventHistoryStats::readFromParcel(const Parcel* input) {
status_t err = input->readUint64(&frameNumber);
if (err != NO_ERROR) return err;
+ if (flags::frametimestamps_previousrelease()) {
+ err = input->readUint64(&previousFrameNumber);
+ if (err != NO_ERROR) return err;
+ }
+
bool hasFence = false;
err = input->readBool(&hasFence);
if (err != NO_ERROR) return err;
@@ -111,12 +125,14 @@ JankData::JankData()
status_t JankData::writeToParcel(Parcel* output) const {
SAFE_PARCEL(output->writeInt64, frameVsyncId);
SAFE_PARCEL(output->writeInt32, jankType);
+ SAFE_PARCEL(output->writeInt64, frameIntervalNs);
return NO_ERROR;
}
status_t JankData::readFromParcel(const Parcel* input) {
SAFE_PARCEL(input->readInt64, &frameVsyncId);
SAFE_PARCEL(input->readInt32, &jankType);
+ SAFE_PARCEL(input->readInt64, &frameIntervalNs);
return NO_ERROR;
}
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 2322b70d1c..38fab9cdaa 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -22,6 +22,7 @@
#include <android/gui/ISurfaceComposerClient.h>
#include <android/native_window.h>
#include <binder/Parcel.h>
+#include <gui/FrameRateUtils.h>
#include <gui/IGraphicBufferProducer.h>
#include <gui/LayerState.h>
#include <gui/SurfaceControl.h>
@@ -83,6 +84,9 @@ layer_state_t::layer_state_t()
frameRateCompatibility(ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT),
changeFrameRateStrategy(ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS),
defaultFrameRateCompatibility(ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT),
+ frameRateCategory(ANATIVEWINDOW_FRAME_RATE_CATEGORY_DEFAULT),
+ frameRateCategorySmoothSwitchOnly(false),
+ frameRateSelectionStrategy(ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_PROPAGATE),
fixedTransformHint(ui::Transform::ROT_INVALID),
autoRefresh(false),
isTrustedOverlay(false),
@@ -158,6 +162,9 @@ status_t layer_state_t::write(Parcel& output) const
SAFE_PARCEL(output.writeByte, frameRateCompatibility);
SAFE_PARCEL(output.writeByte, changeFrameRateStrategy);
SAFE_PARCEL(output.writeByte, defaultFrameRateCompatibility);
+ SAFE_PARCEL(output.writeByte, frameRateCategory);
+ SAFE_PARCEL(output.writeBool, frameRateCategorySmoothSwitchOnly);
+ SAFE_PARCEL(output.writeByte, frameRateSelectionStrategy);
SAFE_PARCEL(output.writeUint32, fixedTransformHint);
SAFE_PARCEL(output.writeBool, autoRefresh);
SAFE_PARCEL(output.writeBool, dimmingEnabled);
@@ -290,6 +297,9 @@ status_t layer_state_t::read(const Parcel& input)
SAFE_PARCEL(input.readByte, &frameRateCompatibility);
SAFE_PARCEL(input.readByte, &changeFrameRateStrategy);
SAFE_PARCEL(input.readByte, &defaultFrameRateCompatibility);
+ SAFE_PARCEL(input.readByte, &frameRateCategory);
+ SAFE_PARCEL(input.readBool, &frameRateCategorySmoothSwitchOnly);
+ SAFE_PARCEL(input.readByte, &frameRateSelectionStrategy);
SAFE_PARCEL(input.readUint32, &tmpUint32);
fixedTransformHint = static_cast<ui::Transform::RotationFlags>(tmpUint32);
SAFE_PARCEL(input.readBool, &autoRefresh);
@@ -659,6 +669,15 @@ void layer_state_t::merge(const layer_state_t& other) {
frameRateCompatibility = other.frameRateCompatibility;
changeFrameRateStrategy = other.changeFrameRateStrategy;
}
+ if (other.what & eFrameRateCategoryChanged) {
+ what |= eFrameRateCategoryChanged;
+ frameRateCategory = other.frameRateCategory;
+ frameRateCategorySmoothSwitchOnly = other.frameRateCategorySmoothSwitchOnly;
+ }
+ if (other.what & eFrameRateSelectionStrategyChanged) {
+ what |= eFrameRateSelectionStrategyChanged;
+ frameRateSelectionStrategy = other.frameRateSelectionStrategy;
+ }
if (other.what & eFixedTransformHintChanged) {
what |= eFixedTransformHintChanged;
fixedTransformHint = other.fixedTransformHint;
@@ -769,6 +788,9 @@ uint64_t layer_state_t::diff(const layer_state_t& other) const {
CHECK_DIFF(diff, eFrameRateSelectionPriority, other, frameRateSelectionPriority);
CHECK_DIFF3(diff, eFrameRateChanged, other, frameRate, frameRateCompatibility,
changeFrameRateStrategy);
+ CHECK_DIFF2(diff, eFrameRateCategoryChanged, other, frameRateCategory,
+ frameRateCategorySmoothSwitchOnly);
+ CHECK_DIFF(diff, eFrameRateSelectionStrategyChanged, other, frameRateSelectionStrategy);
CHECK_DIFF(diff, eFixedTransformHintChanged, other, fixedTransformHint);
CHECK_DIFF(diff, eAutoRefreshChanged, other, autoRefresh);
CHECK_DIFF(diff, eTrustedOverlayChanged, other, isTrustedOverlay);
@@ -855,34 +877,6 @@ status_t InputWindowCommands::read(const Parcel& input) {
return NO_ERROR;
}
-bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy,
- const char* inFunctionName, bool privileged) {
- const char* functionName = inFunctionName != nullptr ? inFunctionName : "call";
- int floatClassification = std::fpclassify(frameRate);
- if (frameRate < 0 || floatClassification == FP_INFINITE || floatClassification == FP_NAN) {
- ALOGE("%s failed - invalid frame rate %f", functionName, frameRate);
- return false;
- }
-
- if (compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT &&
- compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE &&
- (!privileged ||
- (compatibility != ANATIVEWINDOW_FRAME_RATE_EXACT &&
- compatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE))) {
- ALOGE("%s failed - invalid compatibility value %d privileged: %s", functionName,
- compatibility, privileged ? "yes" : "no");
- return false;
- }
-
- if (changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS &&
- changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS) {
- ALOGE("%s failed - invalid change frame rate strategy value %d", functionName,
- changeFrameRateStrategy);
- }
-
- return true;
-}
-
// ----------------------------------------------------------------------------
namespace gui {
@@ -936,7 +930,6 @@ status_t DisplayCaptureArgs::writeToParcel(Parcel* output) const {
SAFE_PARCEL(output->writeStrongBinder, displayToken);
SAFE_PARCEL(output->writeUint32, width);
SAFE_PARCEL(output->writeUint32, height);
- SAFE_PARCEL(output->writeBool, useIdentityTransform);
return NO_ERROR;
}
@@ -946,7 +939,6 @@ status_t DisplayCaptureArgs::readFromParcel(const Parcel* input) {
SAFE_PARCEL(input->readStrongBinder, &displayToken);
SAFE_PARCEL(input->readUint32, &width);
SAFE_PARCEL(input->readUint32, &height);
- SAFE_PARCEL(input->readBool, &useIdentityTransform);
return NO_ERROR;
}
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 53a2f64d11..07a0cfed63 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -43,6 +43,7 @@
#include <gui/AidlStatusUtil.h>
#include <gui/BufferItem.h>
+
#include <gui/IProducerListener.h>
#include <gui/ISurfaceComposer.h>
@@ -50,8 +51,11 @@
#include <private/gui/ComposerService.h>
#include <private/gui/ComposerServiceAIDL.h>
+#include <com_android_graphics_libgui_flags.h>
+
namespace android {
+using namespace com::android::graphics::libgui;
using gui::aidl_utils::statusTFromBinderStatus;
using ui::Dataspace;
@@ -2565,8 +2569,22 @@ void Surface::ProducerListenerProxy::onBuffersDiscarded(const std::vector<int32_
mSurfaceListener->onBuffersDiscarded(discardedBufs);
}
-[[deprecated]] status_t Surface::setFrameRate(float /*frameRate*/, int8_t /*compatibility*/,
- int8_t /*changeFrameRateStrategy*/) {
+status_t Surface::setFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+ if (flags::bq_setframerate()) {
+ status_t err = mGraphicBufferProducer->setFrameRate(frameRate, compatibility,
+ changeFrameRateStrategy);
+ ALOGE_IF(err, "IGraphicBufferProducer::setFrameRate(%.2f) returned %s", frameRate,
+ strerror(-err));
+ return err;
+ }
+#else
+ static_cast<void>(frameRate);
+ static_cast<void>(compatibility);
+ static_cast<void>(changeFrameRateStrategy);
+#endif
+
ALOGI("Surface::setFrameRate is deprecated, setFrameRate hint is dropped as destination is not "
"SurfaceFlinger");
// ISurfaceComposer no longer supports setFrameRate, we will return NO_ERROR when the api is
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 00495ee5f6..8b6f2023dc 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -26,6 +26,7 @@
#include <android/gui/IWindowInfosListener.h>
#include <android/gui/TrustedPresentationThresholds.h>
#include <android/os/IInputConstants.h>
+#include <gui/FrameRateUtils.h>
#include <gui/TraceUtils.h>
#include <utils/Errors.h>
#include <utils/Log.h>
@@ -377,7 +378,6 @@ void TransactionCompletedListener::onTransactionCompleted(ListenerStats listener
}
auto& [callbackFunction, callbackSurfaceControls] = callbacksMap[callbackId];
if (!callbackFunction) {
- ALOGE("cannot call null callback function, skipping");
continue;
}
std::vector<SurfaceControlStats> surfaceControlStats;
@@ -394,6 +394,11 @@ void TransactionCompletedListener::onTransactionCompleted(ListenerStats listener
callbackFunction(transactionStats.latchTime, transactionStats.presentFence,
surfaceControlStats);
+
+ // More than one transaction may contain the same callback id. Erase the callback from
+ // the map to ensure that it is only called once. This can happen if transactions are
+ // parcelled out of process and applied in both processes.
+ callbacksMap.erase(callbackId);
}
// handle on complete callbacks
@@ -446,7 +451,9 @@ void TransactionCompletedListener::onTransactionCompleted(ListenerStats listener
callbackFunction(transactionStats.latchTime, transactionStats.presentFence,
surfaceControlStats);
}
+ }
+ for (const auto& transactionStats : listenerStats.transactionStats) {
for (const auto& surfaceStats : transactionStats.surfaceStats) {
// The callbackMap contains the SurfaceControl object, which we need to look up the
// layerId. Since we don't know which callback contains the SurfaceControl, iterate
@@ -1220,7 +1227,7 @@ status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay
flags |= ISurfaceComposer::eEarlyWakeupEnd;
}
- sp<IBinder> applyToken = mApplyToken ? mApplyToken : sApplyToken;
+ sp<IBinder> applyToken = mApplyToken ? mApplyToken : getDefaultApplyToken();
sp<ISurfaceComposer> sf(ComposerService::getComposerService());
sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags, applyToken,
@@ -1242,11 +1249,15 @@ status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay
sp<IBinder> SurfaceComposerClient::Transaction::sApplyToken = new BBinder();
+std::mutex SurfaceComposerClient::Transaction::sApplyTokenMutex;
+
sp<IBinder> SurfaceComposerClient::Transaction::getDefaultApplyToken() {
+ std::scoped_lock lock{sApplyTokenMutex};
return sApplyToken;
}
void SurfaceComposerClient::Transaction::setDefaultApplyToken(sp<IBinder> applyToken) {
+ std::scoped_lock lock{sApplyTokenMutex};
sApplyToken = applyToken;
}
@@ -2086,6 +2097,32 @@ SurfaceComposerClient::Transaction::setDefaultFrameRateCompatibility(const sp<Su
return *this;
}
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFrameRateCategory(
+ const sp<SurfaceControl>& sc, int8_t category, bool smoothSwitchOnly) {
+ layer_state_t* s = getLayerState(sc);
+ if (!s) {
+ mStatus = BAD_INDEX;
+ return *this;
+ }
+ s->what |= layer_state_t::eFrameRateCategoryChanged;
+ s->frameRateCategory = category;
+ s->frameRateCategorySmoothSwitchOnly = smoothSwitchOnly;
+ return *this;
+}
+
+SurfaceComposerClient::Transaction&
+SurfaceComposerClient::Transaction::setFrameRateSelectionStrategy(const sp<SurfaceControl>& sc,
+ int8_t strategy) {
+ layer_state_t* s = getLayerState(sc);
+ if (!s) {
+ mStatus = BAD_INDEX;
+ return *this;
+ }
+ s->what |= layer_state_t::eFrameRateSelectionStrategyChanged;
+ s->frameRateSelectionStrategy = strategy;
+ return *this;
+}
+
SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFixedTransformHint(
const sp<SurfaceControl>& sc, int32_t fixedTransformHint) {
layer_state_t* s = getLayerState(sc);
@@ -2567,7 +2604,8 @@ void SurfaceComposerClient::getDynamicDisplayInfoInternal(gui::DynamicDisplayInf
outMode.resolution.height = mode.resolution.height;
outMode.xDpi = mode.xDpi;
outMode.yDpi = mode.yDpi;
- outMode.refreshRate = mode.refreshRate;
+ outMode.peakRefreshRate = mode.peakRefreshRate;
+ outMode.vsyncRate = mode.vsyncRate;
outMode.appVsyncOffset = mode.appVsyncOffset;
outMode.sfVsyncOffset = mode.sfVsyncOffset;
outMode.presentationDeadline = mode.presentationDeadline;
@@ -2744,22 +2782,29 @@ status_t SurfaceComposerClient::getHdrOutputConversionSupport(bool* isSupported)
return statusTFromBinderStatus(status);
}
-status_t SurfaceComposerClient::setOverrideFrameRate(uid_t uid, float frameRate) {
+status_t SurfaceComposerClient::setGameModeFrameRateOverride(uid_t uid, float frameRate) {
+ binder::Status status =
+ ComposerServiceAIDL::getComposerService()->setGameModeFrameRateOverride(uid, frameRate);
+ return statusTFromBinderStatus(status);
+}
+
+status_t SurfaceComposerClient::setGameDefaultFrameRateOverride(uid_t uid, float frameRate) {
binder::Status status =
- ComposerServiceAIDL::getComposerService()->setOverrideFrameRate(uid, frameRate);
+ ComposerServiceAIDL::getComposerService()->setGameDefaultFrameRateOverride(uid,
+ frameRate);
return statusTFromBinderStatus(status);
}
-status_t SurfaceComposerClient::updateSmallAreaDetection(std::vector<int32_t>& uids,
+status_t SurfaceComposerClient::updateSmallAreaDetection(std::vector<int32_t>& appIds,
std::vector<float>& thresholds) {
binder::Status status =
- ComposerServiceAIDL::getComposerService()->updateSmallAreaDetection(uids, thresholds);
+ ComposerServiceAIDL::getComposerService()->updateSmallAreaDetection(appIds, thresholds);
return statusTFromBinderStatus(status);
}
-status_t SurfaceComposerClient::setSmallAreaDetectionThreshold(uid_t uid, float threshold) {
+status_t SurfaceComposerClient::setSmallAreaDetectionThreshold(int32_t appId, float threshold) {
binder::Status status =
- ComposerServiceAIDL::getComposerService()->setSmallAreaDetectionThreshold(uid,
+ ComposerServiceAIDL::getComposerService()->setSmallAreaDetectionThreshold(appId,
threshold);
return statusTFromBinderStatus(status);
}
@@ -3084,12 +3129,12 @@ status_t ScreenshotClient::captureDisplay(const DisplayCaptureArgs& captureArgs,
return statusTFromBinderStatus(status);
}
-status_t ScreenshotClient::captureDisplay(DisplayId displayId,
+status_t ScreenshotClient::captureDisplay(DisplayId displayId, const gui::CaptureArgs& captureArgs,
const sp<IScreenCaptureListener>& captureListener) {
sp<gui::ISurfaceComposer> s(ComposerServiceAIDL::getComposerService());
if (s == nullptr) return NO_INIT;
- binder::Status status = s->captureDisplayById(displayId.value, captureListener);
+ binder::Status status = s->captureDisplayById(displayId.value, captureArgs, captureListener);
return statusTFromBinderStatus(status);
}
diff --git a/libs/gui/TEST_MAPPING b/libs/gui/TEST_MAPPING
index a4d9e77351..a590c86ceb 100644
--- a/libs/gui/TEST_MAPPING
+++ b/libs/gui/TEST_MAPPING
@@ -2,6 +2,9 @@
"imports": [
{
"path": "frameworks/native/libs/nativewindow"
+ },
+ {
+ "path": "frameworks/native/services/surfaceflinger"
}
],
"presubmit": [
diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp
index 52af9d5114..ba1d196e9c 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -26,6 +26,41 @@
namespace android::gui {
+namespace {
+
+std::ostream& operator<<(std::ostream& out, const sp<IBinder>& binder) {
+ if (binder == nullptr) {
+ out << "<null>";
+ } else {
+ out << binder.get();
+ }
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const Region& region) {
+ if (region.isEmpty()) {
+ out << "<empty>";
+ return out;
+ }
+
+ bool first = true;
+ Region::const_iterator cur = region.begin();
+ Region::const_iterator const tail = region.end();
+ while (cur != tail) {
+ if (first) {
+ first = false;
+ } else {
+ out << "|";
+ }
+ out << "[" << cur->left << "," << cur->top << "][" << cur->right << "," << cur->bottom
+ << "]";
+ cur++;
+ }
+ return out;
+}
+
+} // namespace
+
void WindowInfo::setInputConfig(ftl::Flags<InputConfig> config, bool value) {
if (value) {
inputConfig |= config;
@@ -43,7 +78,7 @@ bool WindowInfo::touchableRegionContainsPoint(int32_t x, int32_t y) const {
}
bool WindowInfo::frameContainsPoint(int32_t x, int32_t y) const {
- return x >= frameLeft && x < frameRight && y >= frameTop && y < frameBottom;
+ return x >= frame.left && x < frame.right && y >= frame.top && y < frame.bottom;
}
bool WindowInfo::supportsSplitTouch() const {
@@ -59,16 +94,14 @@ bool WindowInfo::interceptsStylus() const {
}
bool WindowInfo::overlaps(const WindowInfo* other) const {
- const bool nonEmpty = (frameRight - frameLeft > 0) || (frameBottom - frameTop > 0);
- return nonEmpty && frameLeft < other->frameRight && frameRight > other->frameLeft &&
- frameTop < other->frameBottom && frameBottom > other->frameTop;
+ return !frame.isEmpty() && frame.left < other->frame.right && frame.right > other->frame.left &&
+ frame.top < other->frame.bottom && frame.bottom > other->frame.top;
}
bool WindowInfo::operator==(const WindowInfo& info) const {
return info.token == token && info.id == id && info.name == name &&
- info.dispatchingTimeout == dispatchingTimeout && info.frameLeft == frameLeft &&
- info.frameTop == frameTop && info.frameRight == frameRight &&
- info.frameBottom == frameBottom && info.surfaceInset == surfaceInset &&
+ info.dispatchingTimeout == dispatchingTimeout && info.frame == frame &&
+ info.contentSize == contentSize && info.surfaceInset == surfaceInset &&
info.globalScaleFactor == globalScaleFactor && info.transform == transform &&
info.touchableRegion.hasSameRects(touchableRegion) &&
info.touchOcclusionMode == touchOcclusionMode && info.ownerPid == ownerPid &&
@@ -103,10 +136,9 @@ status_t WindowInfo::writeToParcel(android::Parcel* parcel) const {
parcel->writeInt32(layoutParamsFlags.get()) ?:
parcel->writeInt32(
static_cast<std::underlying_type_t<WindowInfo::Type>>(layoutParamsType)) ?:
- parcel->writeInt32(frameLeft) ?:
- parcel->writeInt32(frameTop) ?:
- parcel->writeInt32(frameRight) ?:
- parcel->writeInt32(frameBottom) ?:
+ parcel->write(frame) ?:
+ parcel->writeInt32(contentSize.width) ?:
+ parcel->writeInt32(contentSize.height) ?:
parcel->writeInt32(surfaceInset) ?:
parcel->writeFloat(globalScaleFactor) ?:
parcel->writeFloat(alpha) ?:
@@ -155,10 +187,9 @@ status_t WindowInfo::readFromParcel(const android::Parcel* parcel) {
// clang-format off
status = parcel->readInt32(&lpFlags) ?:
parcel->readInt32(&lpType) ?:
- parcel->readInt32(&frameLeft) ?:
- parcel->readInt32(&frameTop) ?:
- parcel->readInt32(&frameRight) ?:
- parcel->readInt32(&frameBottom) ?:
+ parcel->read(frame) ?:
+ parcel->readInt32(&contentSize.width) ?:
+ parcel->readInt32(&contentSize.height) ?:
parcel->readInt32(&surfaceInset) ?:
parcel->readFloat(&globalScaleFactor) ?:
parcel->readFloat(&alpha) ?:
@@ -226,4 +257,24 @@ sp<IBinder> WindowInfoHandle::getToken() const {
void WindowInfoHandle::updateFrom(sp<WindowInfoHandle> handle) {
mInfo = handle->mInfo;
}
+
+std::ostream& operator<<(std::ostream& out, const WindowInfoHandle& window) {
+ const WindowInfo& info = *window.getInfo();
+ std::string transform;
+ info.transform.dump(transform, "transform", " ");
+ out << "name=" << info.name << ", id=" << info.id << ", displayId=" << info.displayId
+ << ", inputConfig=" << info.inputConfig.string() << ", alpha=" << info.alpha << ", frame=["
+ << info.frame.left << "," << info.frame.top << "][" << info.frame.right << ","
+ << info.frame.bottom << "], globalScale=" << info.globalScaleFactor
+ << ", applicationInfo.name=" << info.applicationInfo.name
+ << ", applicationInfo.token=" << info.applicationInfo.token
+ << ", touchableRegion=" << info.touchableRegion << ", ownerPid=" << info.ownerPid.toString()
+ << ", ownerUid=" << info.ownerUid.toString() << ", dispatchingTimeout="
+ << std::chrono::duration_cast<std::chrono::milliseconds>(info.dispatchingTimeout).count()
+ << "ms, token=" << info.token.get()
+ << ", touchOcclusionMode=" << ftl::enum_string(info.touchOcclusionMode) << "\n"
+ << transform;
+ return out;
+}
+
} // namespace android::gui
diff --git a/libs/renderengine/mock/Image.cpp b/libs/gui/aidl/android/gui/CaptureArgs.aidl
index 57f4346f8f..920d94980a 100644
--- a/libs/renderengine/mock/Image.cpp
+++ b/libs/gui/aidl/android/gui/CaptureArgs.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 The Android Open Source Project
+ * Copyright 2023 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.
@@ -14,17 +14,6 @@
* limitations under the License.
*/
-#include <renderengine/mock/Image.h>
+package android.gui;
-namespace android {
-namespace renderengine {
-namespace mock {
-
-// The Google Mock documentation recommends explicit non-header instantiations
-// for better compile time performance.
-Image::Image() = default;
-Image::~Image() = default;
-
-} // namespace mock
-} // namespace renderengine
-} // namespace android
+parcelable CaptureArgs cpp_header "gui/DisplayCaptureArgs.h";
diff --git a/libs/gui/aidl/android/gui/DisplayMode.aidl b/libs/gui/aidl/android/gui/DisplayMode.aidl
index ce30426cb5..f605177cfd 100644
--- a/libs/gui/aidl/android/gui/DisplayMode.aidl
+++ b/libs/gui/aidl/android/gui/DisplayMode.aidl
@@ -29,7 +29,9 @@ parcelable DisplayMode {
float yDpi = 0.0f;
int[] supportedHdrTypes;
- float refreshRate = 0.0f;
+ // Some modes have peak refresh rate lower than the panel vsync rate.
+ float peakRefreshRate = 0.0f;
+ float vsyncRate = 0.0f;
long appVsyncOffset = 0;
long sfVsyncOffset = 0;
long presentationDeadline = 0;
diff --git a/libs/gui/aidl/android/gui/IDisplayEventConnection.aidl b/libs/gui/aidl/android/gui/IDisplayEventConnection.aidl
index 9781ca96f4..008ef19244 100644
--- a/libs/gui/aidl/android/gui/IDisplayEventConnection.aidl
+++ b/libs/gui/aidl/android/gui/IDisplayEventConnection.aidl
@@ -18,6 +18,7 @@ package android.gui;
import android.gui.BitTube;
import android.gui.ParcelableVsyncEventData;
+import android.gui.SchedulingPolicy;
/** @hide */
interface IDisplayEventConnection {
@@ -44,4 +45,9 @@ interface IDisplayEventConnection {
* getLatestVsyncEventData() gets the latest vsync event data.
*/
ParcelableVsyncEventData getLatestVsyncEventData();
+
+ /*
+ * getSchedulingPolicy() used in tests to validate the binder thread pririty
+ */
+ SchedulingPolicy getSchedulingPolicy();
}
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index 516d159cc5..e3122bc300 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -16,6 +16,7 @@
package android.gui;
+import android.gui.CaptureArgs;
import android.gui.Color;
import android.gui.CompositionPreference;
import android.gui.ContentSamplingAttributes;
@@ -46,6 +47,7 @@ import android.gui.LayerDebugInfo;
import android.gui.OverlayProperties;
import android.gui.PullAtomData;
import android.gui.ARect;
+import android.gui.SchedulingPolicy;
import android.gui.StalledTransactionInfo;
import android.gui.StaticDisplayInfo;
import android.gui.WindowInfosListenerInfo;
@@ -231,20 +233,21 @@ interface ISurfaceComposer {
* The subregion can be optionally rotated. It will also be scaled to
* match the size of the output buffer.
*/
- void captureDisplay(in DisplayCaptureArgs args, IScreenCaptureListener listener);
+ oneway void captureDisplay(in DisplayCaptureArgs args, IScreenCaptureListener listener);
/**
* Capture the specified screen. This requires the READ_FRAME_BUFFER
* permission.
*/
- void captureDisplayById(long displayId, IScreenCaptureListener listener);
+ oneway void captureDisplayById(long displayId, in CaptureArgs args,
+ IScreenCaptureListener listener);
/**
* Capture a subtree of the layer hierarchy, potentially ignoring the root node.
* This requires READ_FRAME_BUFFER permission. This function will fail if there
* is a secure window on screen
*/
- void captureLayers(in LayerCaptureArgs args, IScreenCaptureListener listener);
+ oneway void captureLayers(in LayerCaptureArgs args, IScreenCaptureListener listener);
/**
* Clears the frame statistics for animations.
@@ -281,8 +284,6 @@ interface ISurfaceComposer {
*/
List<LayerDebugInfo> getLayerDebugInfo();
- boolean getColorManagement();
-
/**
* Gets the composition preference of the default data space and default pixel format,
* as well as the wide color gamut data space and wide color gamut pixel format.
@@ -476,19 +477,61 @@ interface ISurfaceComposer {
/**
* Set the override frame rate for a specified uid by GameManagerService.
+ * This override is controlled by game mode interventions.
+ * Passing the frame rate and uid to SurfaceFlinger to update the override mapping
+ * in the LayerHistory.
+ */
+ void setGameModeFrameRateOverride(int uid, float frameRate);
+
+ /**
+ * Set the override frame rate for a specified uid by GameManagerService.
+ * This override is controlled by game default frame rate sysprop:
+ * "ro.surface_flinger.game_default_frame_rate_override" holding the override value,
+ * "persisit.graphics.game_default_frame_rate.enabled" to determine if it's enabled.
* Passing the frame rate and uid to SurfaceFlinger to update the override mapping
* in the scheduler.
*/
- void setOverrideFrameRate(int uid, float frameRate);
+ void setGameDefaultFrameRateOverride(int uid, float frameRate);
- oneway void updateSmallAreaDetection(in int[] uids, in float[] thresholds);
+ oneway void updateSmallAreaDetection(in int[] appIds, in float[] thresholds);
/**
- * Set the small area detection threshold for a specified uid by SmallAreaDetectionController.
- * Passing the threshold and uid to SurfaceFlinger to update the uid-threshold mapping
+ * Set the small area detection threshold for a specified appId by SmallAreaDetectionController.
+ * Passing the threshold and appId to SurfaceFlinger to update the appId-threshold mapping
* in the scheduler.
*/
- oneway void setSmallAreaDetectionThreshold(int uid, float threshold);
+ oneway void setSmallAreaDetectionThreshold(int appId, float threshold);
+
+ /**
+ * Enables or disables the frame rate overlay in the top left corner.
+ * Requires root or android.permission.HARDWARE_TEST
+ */
+ void enableRefreshRateOverlay(boolean active);
+
+ /**
+ * Enables or disables the debug flash.
+ * Requires root or android.permission.HARDWARE_TEST
+ */
+ void setDebugFlash(int delay);
+
+ /**
+ * Force composite ahead of next VSYNC.
+ * Requires root or android.permission.HARDWARE_TEST
+ */
+ void scheduleComposite();
+
+ /**
+ * Force commit ahead of next VSYNC.
+ * Requires root or android.permission.HARDWARE_TEST
+ */
+ void scheduleCommit();
+
+ /**
+ * Force all window composition to the GPU (i.e. disable Hardware Overlays).
+ * This can help check if there is a bug in HW Composer.
+ * Requires root or android.permission.HARDWARE_TEST
+ */
+ void forceClientComposition(boolean enabled);
/**
* Gets priority of the RenderEngine in SurfaceFlinger.
@@ -523,4 +566,6 @@ interface ISurfaceComposer {
* applied in SurfaceFlinger due to an unsignaled fence. Otherwise, null is returned.
*/
@nullable StalledTransactionInfo getStalledTransactionInfo(int pid);
+
+ SchedulingPolicy getSchedulingPolicy();
}
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl b/libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl
index 68781ce953..920257c449 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl
@@ -19,6 +19,7 @@ package android.gui;
import android.gui.CreateSurfaceResult;
import android.gui.FrameStats;
import android.gui.LayerMetadata;
+import android.gui.SchedulingPolicy;
/** @hide */
interface ISurfaceComposerClient {
@@ -60,4 +61,6 @@ interface ISurfaceComposerClient {
CreateSurfaceResult mirrorSurface(IBinder mirrorFromHandle);
CreateSurfaceResult mirrorDisplay(long displayId);
+
+ SchedulingPolicy getSchedulingPolicy();
}
diff --git a/libs/renderengine/mock/Framebuffer.cpp b/libs/gui/aidl/android/gui/SchedulingPolicy.aidl
index fbdcaab697..4f7cf0a493 100644
--- a/libs/renderengine/mock/Framebuffer.cpp
+++ b/libs/gui/aidl/android/gui/SchedulingPolicy.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 The Android Open Source Project
+ * Copyright 2023 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.
@@ -14,17 +14,10 @@
* limitations under the License.
*/
-#include <renderengine/mock/Framebuffer.h>
+package android.gui;
-namespace android {
-namespace renderengine {
-namespace mock {
-
-// The Google Mock documentation recommends explicit non-header instantiations
-// for better compile time performance.
-Framebuffer::Framebuffer() = default;
-Framebuffer::~Framebuffer() = default;
-
-} // namespace mock
-} // namespace renderengine
-} // namespace android
+/** @hide */
+parcelable SchedulingPolicy {
+ int policy;
+ int priority;
+}
diff --git a/libs/gui/android/gui/TouchOcclusionMode.aidl b/libs/gui/android/gui/TouchOcclusionMode.aidl
index d91d052135..ed721054df 100644
--- a/libs/gui/android/gui/TouchOcclusionMode.aidl
+++ b/libs/gui/android/gui/TouchOcclusionMode.aidl
@@ -43,5 +43,6 @@ enum TouchOcclusionMode {
* The window won't count for touch occlusion rules if the touch passes
* through it.
*/
- ALLOW
+ ALLOW,
+ ftl_last=ALLOW,
}
diff --git a/libs/gui/fuzzer/Android.bp b/libs/gui/fuzzer/Android.bp
index 82e1b5ae4d..cd738acde2 100644
--- a/libs/gui/fuzzer/Android.bp
+++ b/libs/gui/fuzzer/Android.bp
@@ -24,6 +24,7 @@ package {
cc_defaults {
name: "libgui_fuzzer_defaults",
+ defaults: ["android.hardware.power-ndk_shared"],
static_libs: [
"android.hidl.token@1.0-utils",
"libbinder_random_parcel",
@@ -46,7 +47,6 @@ cc_defaults {
"android.hardware.configstore-utils",
"android.hardware.graphics.bufferqueue@1.0",
"android.hardware.graphics.bufferqueue@2.0",
- "android.hardware.power-V4-cpp",
"android.hidl.token@1.0",
"libSurfaceFlingerProp",
"libgui",
@@ -72,6 +72,14 @@ cc_defaults {
"android-media-fuzzing-reports@google.com",
],
componentid: 155276,
+ hotlists: [
+ "4593311",
+ ],
+ description: "The fuzzer targets the APIs of libgui library",
+ vector: "local_no_privileges_required",
+ service_privilege: "privileged",
+ users: "multi_user",
+ fuzzed_code_usage: "shipped",
},
}
@@ -82,6 +90,7 @@ cc_fuzz {
],
defaults: [
"libgui_fuzzer_defaults",
+ "service_fuzzer_defaults",
],
}
@@ -92,6 +101,7 @@ cc_fuzz {
],
defaults: [
"libgui_fuzzer_defaults",
+ "service_fuzzer_defaults",
],
}
diff --git a/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp b/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp
index 17f4c630ce..2e270b721f 100644
--- a/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp
+++ b/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp
@@ -141,8 +141,8 @@ void BufferQueueFuzzer::invokeBlastBufferQueue() {
CompositorTiming compTiming;
sp<Fence> previousFence = new Fence(memfd_create("pfd", MFD_ALLOW_SEALING));
sp<Fence> gpuFence = new Fence(memfd_create("gfd", MFD_ALLOW_SEALING));
- FrameEventHistoryStats frameStats(frameNumber, gpuFence, compTiming,
- mFdp.ConsumeIntegral<int64_t>(),
+ FrameEventHistoryStats frameStats(frameNumber, mFdp.ConsumeIntegral<uint64_t>(), gpuFence,
+ compTiming, mFdp.ConsumeIntegral<int64_t>(),
mFdp.ConsumeIntegral<int64_t>());
std::vector<SurfaceControlStats> stats;
sp<Fence> presentFence = new Fence(memfd_create("fd", MFD_ALLOW_SEALING));
diff --git a/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp b/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp
index 6e4f074825..0d2a52b576 100644
--- a/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp
+++ b/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp
@@ -62,7 +62,10 @@ DisplayEventReceiver::Event buildDisplayEvent(FuzzedDataProvider* fdp, uint32_t
}
case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG: {
- event.hotplug = DisplayEventReceiver::Event::Hotplug{fdp->ConsumeBool() /*connected*/};
+ event.hotplug =
+ DisplayEventReceiver::Event::Hotplug{fdp->ConsumeBool() /*connected*/,
+ fdp->ConsumeIntegral<
+ int32_t>() /*connectionError*/};
break;
}
case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE: {
diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h
index 67915f905e..9933680c4b 100644
--- a/libs/gui/fuzzer/libgui_fuzzer_utils.h
+++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h
@@ -100,8 +100,8 @@ public:
MOCK_METHOD(binder::Status, setGameContentType, (const sp<IBinder>&, bool), (override));
MOCK_METHOD(binder::Status, captureDisplay,
(const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&), (override));
- MOCK_METHOD(binder::Status, captureDisplayById, (int64_t, const sp<IScreenCaptureListener>&),
- (override));
+ MOCK_METHOD(binder::Status, captureDisplayById,
+ (int64_t, const gui::CaptureArgs&, const sp<IScreenCaptureListener>&), (override));
MOCK_METHOD(binder::Status, captureLayers,
(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&), (override));
MOCK_METHOD(binder::Status, clearAnimationFrameStats, (), (override));
@@ -110,7 +110,6 @@ public:
(override));
MOCK_METHOD(binder::Status, onPullAtom, (int32_t, gui::PullAtomData*), (override));
MOCK_METHOD(binder::Status, getLayerDebugInfo, (std::vector<gui::LayerDebugInfo>*), (override));
- MOCK_METHOD(binder::Status, getColorManagement, (bool*), (override));
MOCK_METHOD(binder::Status, getCompositionPreference, (gui::CompositionPreference*),
(override));
MOCK_METHOD(binder::Status, getDisplayedContentSamplingAttributes,
@@ -150,7 +149,13 @@ public:
(const gui::Color&, const gui::Color&, float, float, float), (override));
MOCK_METHOD(binder::Status, getDisplayDecorationSupport,
(const sp<IBinder>&, std::optional<gui::DisplayDecorationSupport>*), (override));
- MOCK_METHOD(binder::Status, setOverrideFrameRate, (int32_t, float), (override));
+ MOCK_METHOD(binder::Status, setGameModeFrameRateOverride, (int32_t, float), (override));
+ MOCK_METHOD(binder::Status, setGameDefaultFrameRateOverride, (int32_t, float), (override));
+ MOCK_METHOD(binder::Status, enableRefreshRateOverlay, (bool), (override));
+ MOCK_METHOD(binder::Status, setDebugFlash, (int), (override));
+ MOCK_METHOD(binder::Status, scheduleComposite, (), (override));
+ MOCK_METHOD(binder::Status, scheduleCommit, (), (override));
+ MOCK_METHOD(binder::Status, forceClientComposition, (bool), (override));
MOCK_METHOD(binder::Status, updateSmallAreaDetection,
(const std::vector<int32_t>&, const std::vector<float>&), (override));
MOCK_METHOD(binder::Status, setSmallAreaDetectionThreshold, (int32_t, float), (override));
@@ -163,6 +168,7 @@ public:
MOCK_METHOD(binder::Status, getOverlaySupport, (gui::OverlayProperties*), (override));
MOCK_METHOD(binder::Status, getStalledTransactionInfo,
(int32_t, std::optional<gui::StalledTransactionInfo>*), (override));
+ MOCK_METHOD(binder::Status, getSchedulingPolicy, (gui::SchedulingPolicy*), (override));
};
class FakeBnSurfaceComposerClient : public gui::BnSurfaceComposerClient {
@@ -183,6 +189,8 @@ public:
MOCK_METHOD(binder::Status, mirrorDisplay,
(int64_t displayId, gui::CreateSurfaceResult* outResult), (override));
+
+ MOCK_METHOD(binder::Status, getSchedulingPolicy, (gui::SchedulingPolicy*), (override));
};
class FakeDisplayEventDispatcher : public DisplayEventDispatcher {
@@ -194,6 +202,7 @@ public:
MOCK_METHOD4(dispatchVsync, void(nsecs_t, PhysicalDisplayId, uint32_t, VsyncEventData));
MOCK_METHOD3(dispatchHotplug, void(nsecs_t, PhysicalDisplayId, bool));
+ MOCK_METHOD2(dispatchHotplugConnectionError, void(nsecs_t, int32_t));
MOCK_METHOD4(dispatchModeChanged, void(nsecs_t, PhysicalDisplayId, int32_t, nsecs_t));
MOCK_METHOD2(dispatchNullEvent, void(nsecs_t, PhysicalDisplayId));
MOCK_METHOD3(dispatchFrameRateOverrides,
diff --git a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp
index 95b7f39c11..4daa3be36f 100644
--- a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp
+++ b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#include <android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/Boost.h>
#include <fuzzbinder/libbinder_driver.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
@@ -39,10 +39,13 @@ constexpr ui::ColorMode kColormodes[] = {ui::ColorMode::NATIVE,
ui::ColorMode::BT2100_HLG,
ui::ColorMode::DISPLAY_BT2020};
-constexpr hardware::power::Boost kBoost[] = {
- hardware::power::Boost::INTERACTION, hardware::power::Boost::DISPLAY_UPDATE_IMMINENT,
- hardware::power::Boost::ML_ACC, hardware::power::Boost::AUDIO_LAUNCH,
- hardware::power::Boost::CAMERA_LAUNCH, hardware::power::Boost::CAMERA_SHOT,
+constexpr aidl::android::hardware::power::Boost kBoost[] = {
+ aidl::android::hardware::power::Boost::INTERACTION,
+ aidl::android::hardware::power::Boost::DISPLAY_UPDATE_IMMINENT,
+ aidl::android::hardware::power::Boost::ML_ACC,
+ aidl::android::hardware::power::Boost::AUDIO_LAUNCH,
+ aidl::android::hardware::power::Boost::CAMERA_LAUNCH,
+ aidl::android::hardware::power::Boost::CAMERA_SHOT,
};
constexpr gui::TouchOcclusionMode kMode[] = {
@@ -175,10 +178,8 @@ void SurfaceComposerClientFuzzer::getWindowInfo(gui::WindowInfo* windowInfo) {
windowInfo->name = mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes);
windowInfo->layoutParamsFlags = mFdp.PickValueInArray(kFlags);
windowInfo->layoutParamsType = mFdp.PickValueInArray(kType);
- windowInfo->frameLeft = mFdp.ConsumeIntegral<int32_t>();
- windowInfo->frameTop = mFdp.ConsumeIntegral<int32_t>();
- windowInfo->frameRight = mFdp.ConsumeIntegral<int32_t>();
- windowInfo->frameBottom = mFdp.ConsumeIntegral<int32_t>();
+ windowInfo->frame = Rect(mFdp.ConsumeIntegral<int32_t>(), mFdp.ConsumeIntegral<int32_t>(),
+ mFdp.ConsumeIntegral<int32_t>(), mFdp.ConsumeIntegral<int32_t>());
windowInfo->surfaceInset = mFdp.ConsumeIntegral<int32_t>();
windowInfo->alpha = mFdp.ConsumeFloatingPointInRange<float>(0, 1);
ui::Transform transform(mFdp.PickValueInArray(kOrientation));
@@ -284,7 +285,7 @@ void SurfaceComposerClientFuzzer::invokeSurfaceComposerClient() {
SurfaceComposerClient::doUncacheBufferTransaction(mFdp.ConsumeIntegral<uint64_t>());
SurfaceComposerClient::setDisplayBrightness(displayToken, getBrightness(&mFdp));
- hardware::power::Boost boostId = mFdp.PickValueInArray(kBoost);
+ aidl::android::hardware::power::Boost boostId = mFdp.PickValueInArray(kBoost);
SurfaceComposerClient::notifyPowerBoost((int32_t)boostId);
String8 surfaceName((mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes)).c_str());
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index a49a85984f..0e1a505c69 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -17,9 +17,10 @@
#ifndef ANDROID_GUI_BLAST_BUFFER_QUEUE_H
#define ANDROID_GUI_BLAST_BUFFER_QUEUE_H
-#include <gui/IGraphicBufferProducer.h>
-#include <gui/BufferItemConsumer.h>
#include <gui/BufferItem.h>
+#include <gui/BufferItemConsumer.h>
+
+#include <gui/IGraphicBufferProducer.h>
#include <gui/SurfaceComposerClient.h>
#include <utils/Condition.h>
@@ -30,6 +31,8 @@
#include <thread>
#include <queue>
+#include <com_android_graphics_libgui_flags.h>
+
namespace android {
class BLASTBufferQueue;
@@ -47,8 +50,8 @@ public:
void onDisconnect() override EXCLUDES(mMutex);
void addAndGetFrameTimestamps(const NewFrameEventsEntry* newTimestamps,
FrameEventHistoryDelta* outDelta) override EXCLUDES(mMutex);
- void updateFrameTimestamps(uint64_t frameNumber, nsecs_t refreshStartTime,
- const sp<Fence>& gpuCompositionDoneFence,
+ void updateFrameTimestamps(uint64_t frameNumber, uint64_t previousFrameNumber,
+ nsecs_t refreshStartTime, const sp<Fence>& gpuCompositionDoneFence,
const sp<Fence>& presentFence, const sp<Fence>& prevReleaseFence,
CompositorTiming compositorTiming, nsecs_t latchTime,
nsecs_t dequeueReadyTime) EXCLUDES(mMutex);
@@ -58,6 +61,10 @@ public:
protected:
void onSidebandStreamChanged() override EXCLUDES(mMutex);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+ void onSetFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) override;
+#endif
private:
const wp<BLASTBufferQueue> mBLASTBufferQueue;
diff --git a/libs/gui/include/gui/BufferQueue.h b/libs/gui/include/gui/BufferQueue.h
index 690587f0e6..0948c4d076 100644
--- a/libs/gui/include/gui/BufferQueue.h
+++ b/libs/gui/include/gui/BufferQueue.h
@@ -19,9 +19,12 @@
#include <gui/BufferItem.h>
#include <gui/BufferQueueDefs.h>
+
+#include <gui/IConsumerListener.h>
#include <gui/IGraphicBufferConsumer.h>
#include <gui/IGraphicBufferProducer.h>
-#include <gui/IConsumerListener.h>
+
+#include <com_android_graphics_libgui_flags.h>
namespace android {
@@ -69,6 +72,10 @@ public:
void addAndGetFrameTimestamps(
const NewFrameEventsEntry* newTimestamps,
FrameEventHistoryDelta* outDelta) override;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+ void onSetFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) override;
+#endif
private:
// mConsumerListener is a weak reference to the IConsumerListener. This is
// the raison d'etre of ProxyConsumerListener.
diff --git a/libs/gui/include/gui/BufferQueueProducer.h b/libs/gui/include/gui/BufferQueueProducer.h
index 1d13dab623..de47483dca 100644
--- a/libs/gui/include/gui/BufferQueueProducer.h
+++ b/libs/gui/include/gui/BufferQueueProducer.h
@@ -18,6 +18,7 @@
#define ANDROID_GUI_BUFFERQUEUEPRODUCER_H
#include <gui/BufferQueueDefs.h>
+
#include <gui/IGraphicBufferProducer.h>
namespace android {
@@ -201,6 +202,11 @@ public:
// See IGraphicBufferProducer::setAutoPrerotation
virtual status_t setAutoPrerotation(bool autoPrerotation);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+ // See IGraphicBufferProducer::setFrameRate
+ status_t setFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) override;
+#endif
protected:
// see IGraphicsBufferProducer::setMaxDequeuedBufferCount, but with the ability to retrieve the
diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h
index 1df9b11432..9fef512b64 100644
--- a/libs/gui/include/gui/Choreographer.h
+++ b/libs/gui/include/gui/Choreographer.h
@@ -110,6 +110,7 @@ private:
void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count,
VsyncEventData vsyncEventData) override;
void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
+ void dispatchHotplugConnectionError(nsecs_t timestamp, int32_t connectionError) override;
void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
nsecs_t vsyncPeriod) override;
void dispatchNullEvent(nsecs_t, PhysicalDisplayId) override;
@@ -137,4 +138,4 @@ private:
static constexpr size_t kMaxStartTimes = 250;
};
-} // namespace android \ No newline at end of file
+} // namespace android
diff --git a/libs/gui/include/gui/DisplayCaptureArgs.h b/libs/gui/include/gui/DisplayCaptureArgs.h
index 2676e0a338..e29ce41bd5 100644
--- a/libs/gui/include/gui/DisplayCaptureArgs.h
+++ b/libs/gui/include/gui/DisplayCaptureArgs.h
@@ -76,7 +76,6 @@ struct DisplayCaptureArgs : CaptureArgs {
sp<IBinder> displayToken;
uint32_t width{0};
uint32_t height{0};
- bool useIdentityTransform{false};
status_t writeToParcel(Parcel* output) const override;
status_t readFromParcel(const Parcel* input) override;
diff --git a/libs/gui/include/gui/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h
index 140efa6d97..fe2dd206ed 100644
--- a/libs/gui/include/gui/DisplayEventDispatcher.h
+++ b/libs/gui/include/gui/DisplayEventDispatcher.h
@@ -53,6 +53,9 @@ private:
VsyncEventData vsyncEventData) = 0;
virtual void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId,
bool connected) = 0;
+
+ virtual void dispatchHotplugConnectionError(nsecs_t timestamp, int32_t connectionError) = 0;
+
virtual void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
nsecs_t vsyncPeriod) = 0;
// AChoreographer-specific hook for processing null-events so that looper
diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h
index 7fd6c35c5e..79582ce685 100644
--- a/libs/gui/include/gui/DisplayEventReceiver.h
+++ b/libs/gui/include/gui/DisplayEventReceiver.h
@@ -88,6 +88,7 @@ public:
struct Hotplug {
bool connected;
+ int32_t connectionError __attribute__((aligned(4)));
};
struct ModeChange {
diff --git a/libs/renderengine/include/renderengine/Image.h b/libs/gui/include/gui/FrameRateUtils.h
index 3bb47318ef..16896efe1f 100644
--- a/libs/renderengine/include/renderengine/Image.h
+++ b/libs/gui/include/gui/FrameRateUtils.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 The Android Open Source Project
+ * Copyright 2023 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.
@@ -16,16 +16,11 @@
#pragma once
-struct ANativeWindowBuffer;
+#include <stdint.h>
namespace android {
-namespace renderengine {
-class Image {
-public:
- virtual ~Image() = default;
- virtual bool setNativeWindowBuffer(ANativeWindowBuffer* buffer, bool isProtected) = 0;
-};
+bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy,
+ const char* inFunctionName, bool privileged = false);
-} // namespace renderengine
-} // namespace android
+} // namespace android \ No newline at end of file
diff --git a/libs/gui/include/gui/IConsumerListener.h b/libs/gui/include/gui/IConsumerListener.h
index 0ab2399eb2..51d3959de7 100644
--- a/libs/gui/include/gui/IConsumerListener.h
+++ b/libs/gui/include/gui/IConsumerListener.h
@@ -24,6 +24,8 @@
#include <cstdint>
+#include <com_android_graphics_libgui_flags.h>
+
namespace android {
class BufferItem;
@@ -90,6 +92,12 @@ public:
// WARNING: This method can only be called when the BufferQueue is in the consumer's process.
virtual void addAndGetFrameTimestamps(const NewFrameEventsEntry* /*newTimestamps*/,
FrameEventHistoryDelta* /*outDelta*/) {}
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+ // Notifies the consumer of a setFrameRate call from the producer side.
+ virtual void onSetFrameRate(float /*frameRate*/, int8_t /*compatibility*/,
+ int8_t /*changeFrameRateStrategy*/) {}
+#endif
};
#ifndef NO_BINDER
diff --git a/libs/gui/include/gui/IGraphicBufferProducer.h b/libs/gui/include/gui/IGraphicBufferProducer.h
index 98df83453d..7639e709ca 100644
--- a/libs/gui/include/gui/IGraphicBufferProducer.h
+++ b/libs/gui/include/gui/IGraphicBufferProducer.h
@@ -41,6 +41,8 @@
#include <optional>
#include <vector>
+#include <com_android_graphics_libgui_flags.h>
+
namespace android {
// ----------------------------------------------------------------------------
@@ -676,6 +678,12 @@ public:
// the width and height used for dequeueBuffer will be additionally swapped.
virtual status_t setAutoPrerotation(bool autoPrerotation);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+ // Sets the apps intended frame rate.
+ virtual status_t setFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy);
+#endif
+
struct RequestBufferOutput : public Flattenable<RequestBufferOutput> {
RequestBufferOutput() = default;
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index 3ff6735926..a836f4642a 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -199,6 +199,7 @@ public:
SET_BOOT_DISPLAY_MODE, // Deprecated. Autogenerated by .aidl now.
CLEAR_BOOT_DISPLAY_MODE, // Deprecated. Autogenerated by .aidl now.
SET_OVERRIDE_FRAME_RATE, // Deprecated. Autogenerated by .aidl now.
+ GET_SCHEDULING_POLICY,
// Always append new enum to the end.
};
diff --git a/libs/gui/include/gui/ITransactionCompletedListener.h b/libs/gui/include/gui/ITransactionCompletedListener.h
index 39bcb4a56c..bc97cd0828 100644
--- a/libs/gui/include/gui/ITransactionCompletedListener.h
+++ b/libs/gui/include/gui/ITransactionCompletedListener.h
@@ -95,15 +95,18 @@ public:
status_t readFromParcel(const Parcel* input) override;
FrameEventHistoryStats() = default;
- FrameEventHistoryStats(uint64_t fn, const sp<Fence>& gpuCompFence, CompositorTiming compTiming,
+ FrameEventHistoryStats(uint64_t frameNumber, uint64_t previousFrameNumber,
+ const sp<Fence>& gpuCompFence, CompositorTiming compTiming,
nsecs_t refreshTime, nsecs_t dequeueReadyTime)
- : frameNumber(fn),
+ : frameNumber(frameNumber),
+ previousFrameNumber(previousFrameNumber),
gpuCompositionDoneFence(gpuCompFence),
compositorTiming(compTiming),
refreshStartTime(refreshTime),
dequeueReadyTime(dequeueReadyTime) {}
uint64_t frameNumber;
+ uint64_t previousFrameNumber;
sp<Fence> gpuCompositionDoneFence;
CompositorTiming compositorTiming;
nsecs_t refreshStartTime;
@@ -120,14 +123,17 @@ public:
status_t readFromParcel(const Parcel* input) override;
JankData();
- JankData(int64_t frameVsyncId, int32_t jankType)
- : frameVsyncId(frameVsyncId), jankType(jankType) {}
+ JankData(int64_t frameVsyncId, int32_t jankType, nsecs_t frameIntervalNs)
+ : frameVsyncId(frameVsyncId), jankType(jankType), frameIntervalNs(frameIntervalNs) {}
// Identifier for the frame submitted with Transaction.setFrameTimelineVsyncId
int64_t frameVsyncId;
// Bitmask of janks that occurred
int32_t jankType;
+
+ // Expected duration of the frame
+ nsecs_t frameIntervalNs;
};
class SurfaceStats : public Parcelable {
diff --git a/libs/gui/include/gui/JankInfo.h b/libs/gui/include/gui/JankInfo.h
index 1dddeba616..1fc80c30d7 100644
--- a/libs/gui/include/gui/JankInfo.h
+++ b/libs/gui/include/gui/JankInfo.h
@@ -18,7 +18,7 @@
namespace android {
-// Jank information tracked by SurfaceFlinger(SF) for perfetto tracing and telemetry.
+// Jank type tracked by SurfaceFlinger(SF) for Perfetto tracing and telemetry.
enum JankType {
// No Jank
None = 0x0,
@@ -46,6 +46,20 @@ enum JankType {
// where the previous frame was presented in the current frame's expected vsync. This pushes the
// current frame to the next vsync. The behavior is similar to BufferStuffing.
SurfaceFlingerStuffing = 0x100,
+ // Frame was dropped, as a newer frame was ready and replaced this frame.
+ Dropped = 0x200,
+};
+
+// Jank severity type tracked by SurfaceFlinger(SF) for Perfetto tracing and telemetry.
+enum class JankSeverityType {
+ // Unknown: not enough information to classify the severity of a jank
+ Unknown = 0,
+ // None: no jank
+ None = 1,
+ // Partial: jank caused by missing the deadline by less than the app's frame interval
+ Partial = 2,
+ // Full: jank caused by missing the deadline by more than the app's frame interval
+ Full = 3,
};
} // namespace android
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 62e5f89d21..e1dc7911a1 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -181,7 +181,7 @@ struct layer_state_t {
eRelativeLayerChanged = 0x00004000,
eReparent = 0x00008000,
eColorChanged = 0x00010000,
- /* unused = 0x00020000, */
+ eFrameRateCategoryChanged = 0x00020000,
eBufferTransformChanged = 0x00040000,
eTransformToDisplayInverseChanged = 0x00080000,
eCropChanged = 0x00100000,
@@ -197,7 +197,7 @@ struct layer_state_t {
eInputInfoChanged = 0x40000000,
eCornerRadiusChanged = 0x80000000,
eDestinationFrameChanged = 0x1'00000000,
- /* unused = 0x2'00000000, */
+ eFrameRateSelectionStrategyChanged = 0x2'00000000,
eBackgroundColorChanged = 0x4'00000000,
eMetadataChanged = 0x8'00000000,
eColorSpaceAgnosticChanged = 0x10'00000000,
@@ -213,7 +213,6 @@ struct layer_state_t {
eTrustedOverlayChanged = 0x4000'00000000,
eDropInputModeChanged = 0x8000'00000000,
eExtendedRangeBrightnessChanged = 0x10000'00000000,
-
};
layer_state_t();
@@ -265,9 +264,12 @@ struct layer_state_t {
// Changes affecting child states.
static constexpr uint64_t AFFECTS_CHILDREN = layer_state_t::GEOMETRY_CHANGES |
layer_state_t::HIERARCHY_CHANGES | layer_state_t::eAlphaChanged |
+ layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBlurRegionsChanged |
layer_state_t::eColorTransformChanged | layer_state_t::eCornerRadiusChanged |
layer_state_t::eFlagsChanged | layer_state_t::eTrustedOverlayChanged |
- layer_state_t::eFrameRateChanged | layer_state_t::eFixedTransformHintChanged;
+ layer_state_t::eFrameRateChanged | layer_state_t::eFrameRateCategoryChanged |
+ layer_state_t::eFrameRateSelectionStrategyChanged |
+ layer_state_t::eFrameRateSelectionPriority | layer_state_t::eFixedTransformHintChanged;
// Changes affecting data sent to input.
static constexpr uint64_t INPUT_CHANGES = layer_state_t::eInputInfoChanged |
@@ -275,8 +277,8 @@ struct layer_state_t {
layer_state_t::eLayerStackChanged;
// Changes that affect the visible region on a display.
- static constexpr uint64_t VISIBLE_REGION_CHANGES =
- layer_state_t::GEOMETRY_CHANGES | layer_state_t::HIERARCHY_CHANGES;
+ static constexpr uint64_t VISIBLE_REGION_CHANGES = layer_state_t::GEOMETRY_CHANGES |
+ layer_state_t::HIERARCHY_CHANGES | layer_state_t::eAlphaChanged;
bool hasValidBuffer() const;
void sanitize(int32_t permissions);
@@ -357,6 +359,13 @@ struct layer_state_t {
// Default frame rate compatibility used to set the layer refresh rate votetype.
int8_t defaultFrameRateCompatibility;
+ // Frame rate category to suggest what frame rate range a surface should run.
+ int8_t frameRateCategory;
+ bool frameRateCategorySmoothSwitchOnly;
+
+ // Strategy of the layer for frame rate selection.
+ int8_t frameRateSelectionStrategy;
+
// Set by window manager indicating the layer and all its children are
// in a different orientation than the display. The hint suggests that
// the graphic producers should receive a transform hint as if the
@@ -472,16 +481,6 @@ static inline int compare_type(const DisplayState& lhs, const DisplayState& rhs)
return compare_type(lhs.token, rhs.token);
}
-// Returns true if the frameRate is valid.
-//
-// @param frameRate the frame rate in Hz
-// @param compatibility a ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_*
-// @param changeFrameRateStrategy a ANATIVEWINDOW_CHANGE_FRAME_RATE_*
-// @param functionName calling function or nullptr. Used for logging
-// @param privileged whether caller has unscoped surfaceflinger access
-bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy,
- const char* functionName, bool privileged = false);
-
}; // namespace android
#endif // ANDROID_SF_LAYER_STATE_H
diff --git a/libs/gui/include/gui/SchedulingPolicy.h b/libs/gui/include/gui/SchedulingPolicy.h
new file mode 100644
index 0000000000..48c9f0c7bf
--- /dev/null
+++ b/libs/gui/include/gui/SchedulingPolicy.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include <sched.h>
+
+#include <android/gui/SchedulingPolicy.h>
+#include <binder/Status.h>
+
+namespace android::gui {
+
+static binder::Status getSchedulingPolicy(gui::SchedulingPolicy* outPolicy) {
+ outPolicy->policy = sched_getscheduler(0);
+ if (outPolicy->policy < 0) {
+ return binder::Status::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+
+ struct sched_param param;
+ if (sched_getparam(0, &param) < 0) {
+ return binder::Status::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ outPolicy->priority = param.sched_priority;
+ return binder::Status::ok();
+}
+
+} // namespace android::gui \ No newline at end of file
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 7c55100784..14e3dd583e 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -201,17 +201,23 @@ public:
// Sets the frame rate of a particular app (uid). This is currently called
// by GameManager.
- static status_t setOverrideFrameRate(uid_t uid, float frameRate);
+ static status_t setGameModeFrameRateOverride(uid_t uid, float frameRate);
- // Update the small area detection whole uid-threshold mappings by same size uid and threshold
- // vector.
+ // Sets the frame rate of a particular app (uid). This is currently called
+ // by GameManager and controlled by two sysprops:
+ // "ro.surface_flinger.game_default_frame_rate_override" holding the override value,
+ // "persisit.graphics.game_default_frame_rate.enabled" to determine if it's enabled.
+ static status_t setGameDefaultFrameRateOverride(uid_t uid, float frameRate);
+
+ // Update the small area detection whole appId-threshold mappings by same size appId and
+ // threshold vector.
// Ref:setSmallAreaDetectionThreshold.
- static status_t updateSmallAreaDetection(std::vector<int32_t>& uids,
+ static status_t updateSmallAreaDetection(std::vector<int32_t>& appIds,
std::vector<float>& thresholds);
- // Sets the small area detection threshold to particular apps (uid). Passing value 0 means
+ // Sets the small area detection threshold to particular apps (appId). Passing value 0 means
// to disable small area detection to the app.
- static status_t setSmallAreaDetectionThreshold(uid_t uid, float threshold);
+ static status_t setSmallAreaDetectionThreshold(int32_t appId, float threshold);
// Switches on/off Auto Low Latency Mode on the connected display. This should only be
// called if the connected display supports Auto Low Latency Mode as reported by
@@ -422,6 +428,7 @@ public:
class Transaction : public Parcelable {
private:
static sp<IBinder> sApplyToken;
+ static std::mutex sApplyTokenMutex;
void releaseBufferIfOverwriting(const layer_state_t& state);
static void mergeFrameTimelineInfo(FrameTimelineInfo& t, const FrameTimelineInfo& other);
@@ -685,6 +692,11 @@ public:
Transaction& setDefaultFrameRateCompatibility(const sp<SurfaceControl>& sc,
int8_t compatibility);
+ Transaction& setFrameRateCategory(const sp<SurfaceControl>& sc, int8_t category,
+ bool smoothSwitchOnly);
+
+ Transaction& setFrameRateSelectionStrategy(const sp<SurfaceControl>& sc, int8_t strategy);
+
// Set by window manager indicating the layer and all its children are
// in a different orientation than the display. The hint suggests that
// the graphic producers should receive a transform hint as if the
@@ -835,8 +847,14 @@ private:
class ScreenshotClient {
public:
static status_t captureDisplay(const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&);
- static status_t captureDisplay(DisplayId, const sp<IScreenCaptureListener>&);
+ static status_t captureDisplay(DisplayId, const gui::CaptureArgs&,
+ const sp<IScreenCaptureListener>&);
static status_t captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&);
+
+ [[deprecated]] static status_t captureDisplay(DisplayId id,
+ const sp<IScreenCaptureListener>& listener) {
+ return captureDisplay(id, gui::CaptureArgs(), listener);
+ }
};
// ---------------------------------------------------------------------------
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index 7ff73874ae..4d4c5e4394 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -26,6 +26,7 @@
#include <gui/constants.h>
#include <ui/Rect.h>
#include <ui/Region.h>
+#include <ui/Size.h>
#include <ui/Transform.h>
#include <utils/RefBase.h>
#include <utils/Timers.h>
@@ -194,10 +195,10 @@ struct WindowInfo : public Parcelable {
std::chrono::nanoseconds dispatchingTimeout = std::chrono::seconds(5);
/* These values are filled in by SurfaceFlinger. */
- int32_t frameLeft = -1;
- int32_t frameTop = -1;
- int32_t frameRight = -1;
- int32_t frameBottom = -1;
+ Rect frame = Rect::INVALID_RECT;
+
+ // The real size of the content, excluding any crop. If no buffer is rendered, this is 0,0
+ ui::Size contentSize = ui::Size(0, 0);
/*
* SurfaceFlinger consumes this value to shrink the computed frame. This is
@@ -314,4 +315,7 @@ protected:
WindowInfo mInfo;
};
+
+std::ostream& operator<<(std::ostream& out, const WindowInfoHandle& window);
+
} // namespace android::gui
diff --git a/libs/gui/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig
new file mode 100644
index 0000000000..b081030c9f
--- /dev/null
+++ b/libs/gui/libgui_flags.aconfig
@@ -0,0 +1,17 @@
+package: "com.android.graphics.libgui.flags"
+
+flag {
+ name: "bq_setframerate"
+ namespace: "core_graphics"
+ description: "This flag controls plumbing setFrameRate thru BufferQueue"
+ bug: "281695725"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "frametimestamps_previousrelease"
+ namespace: "core_graphics"
+ description: "Controls a fence fixup for timestamp apis"
+ bug: "310927247"
+ is_fixed_read_only: true
+}
diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp
index 462ce6e14f..6dcd501404 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -15,9 +15,13 @@ cc_test {
name: "libgui_test",
test_suites: ["device-tests"],
- cflags: [
+ defaults: ["libgui-defaults"],
+
+ cppflags: [
"-Wall",
"-Werror",
+ "-Wno-extra",
+ "-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_BQ_SETFRAMERATE=true",
],
srcs: [
@@ -28,6 +32,7 @@ cc_test {
"CompositorTiming_test.cpp",
"CpuConsumer_test.cpp",
"EndToEndNativeInputTest.cpp",
+ "FrameRateUtilsTest.cpp",
"DisplayInfo_test.cpp",
"DisplayedContentSampling_test.cpp",
"FillBuffer.cpp",
@@ -53,19 +58,12 @@ cc_test {
"android.hardware.configstore@1.0",
"android.hardware.configstore-utils",
"libSurfaceFlingerProp",
- "libbase",
- "liblog",
- "libEGL",
"libGLESv1_CM",
- "libGLESv2",
- "libbinder",
- "libcutils",
- "libgui",
- "libhidlbase",
"libinput",
- "libui",
- "libutils",
- "libnativewindow",
+ ],
+
+ static_libs: [
+ "libgmock",
],
header_libs: ["libsurfaceflinger_headers"],
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index cd90168784..ea7078dd05 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -31,17 +31,23 @@
#include <gui/test/CallbackUtils.h>
#include <private/gui/ComposerService.h>
#include <private/gui/ComposerServiceAIDL.h>
+#include <tests/utils/ScreenshotUtils.h>
#include <ui/DisplayMode.h>
#include <ui/DisplayState.h>
#include <ui/GraphicBuffer.h>
#include <ui/GraphicTypes.h>
+#include <ui/Rect.h>
+#include <ui/Size.h>
#include <ui/Transform.h>
#include <gtest/gtest.h>
+#include <com_android_graphics_libgui_flags.h>
+
using namespace std::chrono_literals;
namespace android {
+using namespace com::android::graphics::libgui;
using Transaction = SurfaceComposerClient::Transaction;
using android::hardware::graphics::common::V1_2::BufferUsage;
@@ -197,18 +203,23 @@ protected:
ALOGD("Display: %dx%d orientation:%d", mDisplayWidth, mDisplayHeight,
displayState.orientation);
+ mRootSurfaceControl = mClient->createSurface(String8("RootTestSurface"), mDisplayWidth,
+ mDisplayHeight, PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eFXSurfaceBufferState,
+ /*parent*/ nullptr);
+
+ t.setLayerStack(mRootSurfaceControl, ui::DEFAULT_LAYER_STACK)
+ .setLayer(mRootSurfaceControl, std::numeric_limits<int32_t>::max())
+ .show(mRootSurfaceControl)
+ .apply();
+
mSurfaceControl = mClient->createSurface(String8("TestSurface"), mDisplayWidth,
mDisplayHeight, PIXEL_FORMAT_RGBA_8888,
ISurfaceComposerClient::eFXSurfaceBufferState,
- /*parent*/ nullptr);
- t.setLayerStack(mSurfaceControl, ui::DEFAULT_LAYER_STACK)
- .setLayer(mSurfaceControl, std::numeric_limits<int32_t>::max())
- .show(mSurfaceControl)
- .setDataspace(mSurfaceControl, ui::Dataspace::V0_SRGB)
- .apply();
+ /*parent*/ mRootSurfaceControl->getHandle());
- mCaptureArgs.displayToken = mDisplayToken;
- mCaptureArgs.dataspace = ui::Dataspace::V0_SRGB;
+ mCaptureArgs.sourceCrop = Rect(ui::Size(mDisplayWidth, mDisplayHeight));
+ mCaptureArgs.layerHandle = mRootSurfaceControl->getHandle();
}
void setUpProducer(BLASTBufferQueueHelper& adapter, sp<IGraphicBufferProducer>& producer,
@@ -295,21 +306,6 @@ protected:
captureBuf->unlock();
}
- static status_t captureDisplay(DisplayCaptureArgs& captureArgs,
- ScreenCaptureResults& captureResults) {
- const auto sf = ComposerServiceAIDL::getComposerService();
- SurfaceComposerClient::Transaction().apply(true);
-
- const sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener();
- binder::Status status = sf->captureDisplay(captureArgs, captureListener);
- status_t err = gui::aidl_utils::statusTFromBinderStatus(status);
- if (err != NO_ERROR) {
- return err;
- }
- captureResults = captureListener->waitForResults();
- return fenceStatus(captureResults.fenceResult);
- }
-
void queueBuffer(sp<IGraphicBufferProducer> igbp, uint8_t r, uint8_t g, uint8_t b,
nsecs_t presentTimeDelay) {
int slot;
@@ -342,11 +338,12 @@ protected:
sp<IBinder> mDisplayToken;
sp<SurfaceControl> mSurfaceControl;
+ sp<SurfaceControl> mRootSurfaceControl;
uint32_t mDisplayWidth;
uint32_t mDisplayHeight;
- DisplayCaptureArgs mCaptureArgs;
+ LayerCaptureArgs mCaptureArgs;
ScreenCaptureResults mCaptureResults;
sp<CountProducerListener> mProducerListener;
};
@@ -364,7 +361,9 @@ TEST_F(BLASTBufferQueueTest, Update) {
BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
sp<SurfaceControl> updateSurface =
mClient->createSurface(String8("UpdateTest"), mDisplayWidth / 2, mDisplayHeight / 2,
- PIXEL_FORMAT_RGBA_8888);
+ PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eFXSurfaceBufferState,
+ /*parent*/ mRootSurfaceControl->getHandle());
adapter.update(updateSurface, mDisplayWidth / 2, mDisplayHeight / 2);
ASSERT_EQ(updateSurface, adapter.getSurfaceControl());
sp<IGraphicBufferProducer> igbProducer;
@@ -447,10 +446,11 @@ TEST_F(BLASTBufferQueueTest, onFrameAvailable_Apply) {
igbProducer->queueBuffer(slot, input, &qbOutput);
ASSERT_NE(ui::Transform::ROT_INVALID, qbOutput.transformHint);
- adapter.waitForCallbacks();
+ // ensure the buffer queue transaction has been committed
+ Transaction().apply(true /* synchronous */);
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
}
@@ -531,9 +531,11 @@ TEST_F(BLASTBufferQueueTest, SetCrop_Item) {
igbProducer->queueBuffer(slot, input, &qbOutput);
ASSERT_NE(ui::Transform::ROT_INVALID, qbOutput.transformHint);
- adapter.waitForCallbacks();
+ // ensure the buffer queue transaction has been committed
+ Transaction().apply(true /* synchronous */);
+
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(r, g, b,
@@ -549,16 +551,6 @@ TEST_F(BLASTBufferQueueTest, SetCrop_ScalingModeScaleCrop) {
(mDisplayWidth < mDisplayHeight) ? mDisplayWidth / 2 : mDisplayHeight / 2;
int32_t finalCropSideLength = bufferSideLength / 2;
- auto bg = mClient->createSurface(String8("BGTest"), 0, 0, PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eFXSurfaceEffect);
- ASSERT_NE(nullptr, bg.get());
- Transaction t;
- t.setLayerStack(bg, ui::DEFAULT_LAYER_STACK)
- .setCrop(bg, Rect(0, 0, mDisplayWidth, mDisplayHeight))
- .setColor(bg, half3{0, 0, 0})
- .setLayer(bg, 0)
- .apply();
-
BLASTBufferQueueHelper adapter(mSurfaceControl, bufferSideLength, bufferSideLength);
sp<IGraphicBufferProducer> igbProducer;
setUpProducer(adapter, igbProducer);
@@ -590,9 +582,11 @@ TEST_F(BLASTBufferQueueTest, SetCrop_ScalingModeScaleCrop) {
igbProducer->queueBuffer(slot, input, &qbOutput);
ASSERT_NE(ui::Transform::ROT_INVALID, qbOutput.transformHint);
- adapter.waitForCallbacks();
+ // ensure the buffer queue transaction has been committed
+ Transaction().apply(true /* synchronous */);
+
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(checkScreenCapture(r, g, b,
{10, 10, (int32_t)bufferSideLength - 10,
(int32_t)bufferSideLength - 10}));
@@ -603,17 +597,6 @@ TEST_F(BLASTBufferQueueTest, SetCrop_ScalingModeScaleCrop) {
}
TEST_F(BLASTBufferQueueTest, ScaleCroppedBufferToBufferSize) {
- // add black background
- auto bg = mClient->createSurface(String8("BGTest"), 0, 0, PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eFXSurfaceEffect);
- ASSERT_NE(nullptr, bg.get());
- Transaction t;
- t.setLayerStack(bg, ui::DEFAULT_LAYER_STACK)
- .setCrop(bg, Rect(0, 0, mDisplayWidth, mDisplayHeight))
- .setColor(bg, half3{0, 0, 0})
- .setLayer(bg, 0)
- .apply();
-
Rect windowSize(1000, 1000);
Rect bufferSize(windowSize);
Rect bufferCrop(200, 200, 700, 700);
@@ -653,9 +636,10 @@ TEST_F(BLASTBufferQueueTest, ScaleCroppedBufferToBufferSize) {
igbProducer->queueBuffer(slot, input, &qbOutput);
ASSERT_NE(ui::Transform::ROT_INVALID, qbOutput.transformHint);
- adapter.waitForCallbacks();
+ // ensure the buffer queue transaction has been committed
+ Transaction().apply(true /* synchronous */);
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
// Verify cropped region is scaled correctly.
ASSERT_NO_FATAL_FAILURE(checkScreenCapture(255, 0, 0, {10, 10, 490, 490}));
@@ -670,17 +654,6 @@ TEST_F(BLASTBufferQueueTest, ScaleCroppedBufferToBufferSize) {
}
TEST_F(BLASTBufferQueueTest, ScaleCroppedBufferToWindowSize) {
- // add black background
- auto bg = mClient->createSurface(String8("BGTest"), 0, 0, PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eFXSurfaceEffect);
- ASSERT_NE(nullptr, bg.get());
- Transaction t;
- t.setLayerStack(bg, ui::DEFAULT_LAYER_STACK)
- .setCrop(bg, Rect(0, 0, mDisplayWidth, mDisplayHeight))
- .setColor(bg, half3{0, 0, 0})
- .setLayer(bg, 0)
- .apply();
-
Rect windowSize(1000, 1000);
Rect bufferSize(500, 500);
Rect bufferCrop(100, 100, 350, 350);
@@ -720,9 +693,10 @@ TEST_F(BLASTBufferQueueTest, ScaleCroppedBufferToWindowSize) {
igbProducer->queueBuffer(slot, input, &qbOutput);
ASSERT_NE(ui::Transform::ROT_INVALID, qbOutput.transformHint);
- adapter.waitForCallbacks();
+ // ensure the buffer queue transaction has been committed
+ Transaction().apply(true /* synchronous */);
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
// Verify cropped region is scaled correctly.
ASSERT_NO_FATAL_FAILURE(checkScreenCapture(255, 0, 0, {10, 10, 490, 490}));
ASSERT_NO_FATAL_FAILURE(checkScreenCapture(0, 255, 0, {10, 510, 490, 990}));
@@ -767,10 +741,12 @@ TEST_F(BLASTBufferQueueTest, ScalingModeChanges) {
NATIVE_WINDOW_SCALING_MODE_FREEZE, 0,
Fence::NO_FENCE);
igbProducer->queueBuffer(slot, input, &qbOutput);
- adapter.waitForCallbacks();
+
+ // ensure the buffer queue transaction has been committed
+ Transaction().apply(true /* synchronous */);
}
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(r, g, b,
@@ -802,10 +778,11 @@ TEST_F(BLASTBufferQueueTest, ScalingModeChanges) {
NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW,
0, Fence::NO_FENCE);
igbProducer->queueBuffer(slot, input, &qbOutput);
- adapter.waitForCallbacks();
+ // ensure the buffer queue transaction has been committed
+ Transaction().apply(true /* synchronous */);
}
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
// verify we still scale the buffer to the new size (half the screen height)
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(r, g, b,
@@ -840,12 +817,12 @@ TEST_F(BLASTBufferQueueTest, SyncThenNoSync) {
transactionCallback.getCallbackData(&callbackData);
// capture screen and verify that it is green
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(0, 255, 0, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
mProducerListener->waitOnNumberReleased(1);
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
}
@@ -885,7 +862,7 @@ TEST_F(BLASTBufferQueueTest, MultipleSyncTransactions) {
transactionCallback.getCallbackData(&callbackData);
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
}
@@ -933,7 +910,7 @@ TEST_F(BLASTBufferQueueTest, MultipleSyncTransactionWithNonSync) {
transactionCallback.getCallbackData(&callbackData);
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
}
@@ -983,7 +960,7 @@ TEST_F(BLASTBufferQueueTest, MultipleSyncRunOutOfBuffers) {
transactionCallback.getCallbackData(&callbackData);
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
}
@@ -1041,7 +1018,7 @@ TEST_F(BLASTBufferQueueTest, RunOutOfBuffersWaitingOnSF) {
transactionCallback.getCallbackData(&callbackData);
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
}
@@ -1074,13 +1051,13 @@ TEST_F(BLASTBufferQueueTest, SyncNextTransactionAcquireMultipleBuffers) {
transactionCallback.getCallbackData(&callbackData);
// capture screen and verify that it is blue
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(0, 0, 255, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
mProducerListener->waitOnNumberReleased(2);
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(255, 0, 0, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
}
@@ -1176,7 +1153,7 @@ TEST_F(BLASTBufferQueueTest, SyncNextTransactionDropBuffer) {
CallbackData callbackData;
transactionCallback.getCallbackData(&callbackData);
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
sync.apply();
@@ -1195,7 +1172,7 @@ TEST_F(BLASTBufferQueueTest, DISABLED_DisconnectProducerTest) {
mClient->createSurface(String8("TestSurface"), mDisplayWidth, mDisplayHeight,
PIXEL_FORMAT_RGBA_8888,
ISurfaceComposerClient::eFXSurfaceBufferState,
- /*parent*/ nullptr);
+ /*parent*/ mRootSurfaceControl->getHandle());
Transaction()
.setLayerStack(mSurfaceControl, ui::DEFAULT_LAYER_STACK)
.setLayer(mSurfaceControl, std::numeric_limits<int32_t>::max())
@@ -1220,7 +1197,7 @@ TEST_F(BLASTBufferQueueTest, DISABLED_DisconnectProducerTest) {
CallbackData callbackData;
transactionCallback.getCallbackData(&callbackData);
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(255, 0, 0,
{0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
@@ -1238,7 +1215,7 @@ TEST_F(BLASTBufferQueueTest, DISABLED_UpdateSurfaceControlTest) {
mClient->createSurface(String8("TestSurface"), mDisplayWidth, mDisplayHeight,
PIXEL_FORMAT_RGBA_8888,
ISurfaceComposerClient::eFXSurfaceBufferState,
- /*parent*/ nullptr);
+ /*parent*/ mRootSurfaceControl->getHandle());
Transaction()
.setLayerStack(mSurfaceControl, ui::DEFAULT_LAYER_STACK)
.setLayer(mSurfaceControl, std::numeric_limits<int32_t>::max())
@@ -1263,7 +1240,7 @@ TEST_F(BLASTBufferQueueTest, DISABLED_UpdateSurfaceControlTest) {
CallbackData callbackData;
transactionCallback.getCallbackData(&callbackData);
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(255, 0, 0,
{0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
@@ -1323,43 +1300,6 @@ TEST_F(BLASTBufferQueueTest, QueryNativeWindowQueuesToWindowComposer) {
ASSERT_EQ(queuesToNativeWindow, 1);
}
-// Test a slow producer doesn't hold up a faster producer from the same client. Essentially tests
-// BBQ uses separate transaction queues.
-TEST_F(BLASTBufferQueueTest, OutOfOrderTransactionTest) {
- sp<SurfaceControl> bgSurface =
- mClient->createSurface(String8("BGTest"), 0, 0, PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eFXSurfaceBufferState);
- ASSERT_NE(nullptr, bgSurface.get());
- Transaction t;
- t.setLayerStack(bgSurface, ui::DEFAULT_LAYER_STACK)
- .show(bgSurface)
- .setDataspace(bgSurface, ui::Dataspace::V0_SRGB)
- .setLayer(bgSurface, std::numeric_limits<int32_t>::max() - 1)
- .apply();
-
- BLASTBufferQueueHelper slowAdapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
- sp<IGraphicBufferProducer> slowIgbProducer;
- setUpProducer(slowAdapter, slowIgbProducer);
- nsecs_t presentTimeDelay = std::chrono::nanoseconds(500ms).count();
- queueBuffer(slowIgbProducer, 0 /* r */, 255 /* g */, 0 /* b */, presentTimeDelay);
-
- BLASTBufferQueueHelper fastAdapter(bgSurface, mDisplayWidth, mDisplayHeight);
- sp<IGraphicBufferProducer> fastIgbProducer;
- setUpProducer(fastAdapter, fastIgbProducer);
- uint8_t r = 255;
- uint8_t g = 0;
- uint8_t b = 0;
- queueBuffer(fastIgbProducer, r, g, b, 0 /* presentTimeDelay */);
- fastAdapter.waitForCallbacks();
-
- // capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
-
- ASSERT_NO_FATAL_FAILURE(
- checkScreenCapture(r, g, b,
- {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight / 2}));
-}
-
TEST_F(BLASTBufferQueueTest, TransformHint) {
// Transform hint is provided to BBQ via the surface control passed by WM
mSurfaceControl->setTransformHint(ui::Transform::ROT_90);
@@ -1432,8 +1372,8 @@ public:
igbProducer->queueBuffer(slot, input, &qbOutput);
ASSERT_NE(ui::Transform::ROT_INVALID, qbOutput.transformHint);
- adapter.waitForCallbacks();
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ Transaction().apply(true /* synchronous */);
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
switch (tr) {
case ui::Transform::ROT_0:
@@ -1644,6 +1584,9 @@ TEST_F(BLASTFrameEventHistoryTest, FrameEventHistory_Basic) {
nsecs_t postedTimeB = 0;
setUpAndQueueBuffer(igbProducer, &requestedPresentTimeB, &postedTimeB, &qbOutput, true);
history.applyDelta(qbOutput.frameTimestamps);
+
+ adapter.waitForCallback(2);
+
events = history.getFrame(1);
ASSERT_NE(nullptr, events);
@@ -1653,7 +1596,9 @@ TEST_F(BLASTFrameEventHistoryTest, FrameEventHistory_Basic) {
ASSERT_GE(events->postedTime, postedTimeA);
ASSERT_GE(events->latchTime, postedTimeA);
- ASSERT_GE(events->dequeueReadyTime, events->latchTime);
+ if (flags::frametimestamps_previousrelease()) {
+ ASSERT_EQ(events->dequeueReadyTime, FrameEvents::TIMESTAMP_PENDING);
+ }
ASSERT_NE(nullptr, events->gpuCompositionDoneFence);
ASSERT_NE(nullptr, events->displayPresentFence);
ASSERT_NE(nullptr, events->releaseFence);
@@ -1665,6 +1610,50 @@ TEST_F(BLASTFrameEventHistoryTest, FrameEventHistory_Basic) {
ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
ASSERT_GE(events->postedTime, postedTimeB);
+ // Now do the same as above with a third buffer, so that timings related to
+ // buffer releases make it back to the first frame.
+ nsecs_t requestedPresentTimeC = 0;
+ nsecs_t postedTimeC = 0;
+ setUpAndQueueBuffer(igbProducer, &requestedPresentTimeC, &postedTimeC, &qbOutput, true);
+ history.applyDelta(qbOutput.frameTimestamps);
+
+ adapter.waitForCallback(3);
+
+ // Check the first frame...
+ events = history.getFrame(1);
+ ASSERT_NE(nullptr, events);
+ ASSERT_EQ(1, events->frameNumber);
+ ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
+ ASSERT_GE(events->postedTime, postedTimeA);
+ ASSERT_GE(events->latchTime, postedTimeA);
+ // Now dequeueReadyTime is valid, because the release timings finally
+ // propaged to queueBuffer()
+ ASSERT_GE(events->dequeueReadyTime, events->latchTime);
+ ASSERT_NE(nullptr, events->gpuCompositionDoneFence);
+ ASSERT_NE(nullptr, events->displayPresentFence);
+ ASSERT_NE(nullptr, events->releaseFence);
+
+ // ...and the second
+ events = history.getFrame(2);
+ ASSERT_NE(nullptr, events);
+ ASSERT_EQ(2, events->frameNumber);
+ ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
+ ASSERT_GE(events->postedTime, postedTimeB);
+ ASSERT_GE(events->latchTime, postedTimeB);
+ if (flags::frametimestamps_previousrelease()) {
+ ASSERT_EQ(events->dequeueReadyTime, FrameEvents::TIMESTAMP_PENDING);
+ }
+ ASSERT_NE(nullptr, events->gpuCompositionDoneFence);
+ ASSERT_NE(nullptr, events->displayPresentFence);
+ ASSERT_NE(nullptr, events->releaseFence);
+
+ // ...and finally the third!
+ events = history.getFrame(3);
+ ASSERT_NE(nullptr, events);
+ ASSERT_EQ(3, events->frameNumber);
+ ASSERT_EQ(requestedPresentTimeC, events->requestedPresentTime);
+ ASSERT_GE(events->postedTime, postedTimeC);
+
// wait for any callbacks that have not been received
adapter.waitForCallbacks();
}
@@ -1723,6 +1712,8 @@ TEST_F(BLASTFrameEventHistoryTest, FrameEventHistory_DroppedFrame) {
setUpAndQueueBuffer(igbProducer, &requestedPresentTimeC, &postedTimeC, &qbOutput, true);
history.applyDelta(qbOutput.frameTimestamps);
+ adapter.waitForCallback(3);
+
// frame number, requestedPresentTime, and postTime should not have changed
ASSERT_EQ(1, events->frameNumber);
ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
@@ -1742,6 +1733,42 @@ TEST_F(BLASTFrameEventHistoryTest, FrameEventHistory_DroppedFrame) {
ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
ASSERT_GE(events->postedTime, postedTimeB);
ASSERT_GE(events->latchTime, postedTimeB);
+
+ if (flags::frametimestamps_previousrelease()) {
+ ASSERT_EQ(events->dequeueReadyTime, FrameEvents::TIMESTAMP_PENDING);
+ }
+ ASSERT_NE(nullptr, events->gpuCompositionDoneFence);
+ ASSERT_NE(nullptr, events->displayPresentFence);
+ ASSERT_NE(nullptr, events->releaseFence);
+
+ // Queue another buffer to check for timestamps that came late
+ nsecs_t requestedPresentTimeD = 0;
+ nsecs_t postedTimeD = 0;
+ setUpAndQueueBuffer(igbProducer, &requestedPresentTimeD, &postedTimeD, &qbOutput, true);
+ history.applyDelta(qbOutput.frameTimestamps);
+
+ adapter.waitForCallback(4);
+
+ // frame number, requestedPresentTime, and postTime should not have changed
+ events = history.getFrame(1);
+ ASSERT_EQ(1, events->frameNumber);
+ ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
+ ASSERT_GE(events->postedTime, postedTimeA);
+
+ // a valid latchtime and pre and post composition info should not be set for the dropped frame
+ ASSERT_FALSE(events->hasLatchInfo());
+ ASSERT_FALSE(events->hasDequeueReadyInfo());
+ ASSERT_FALSE(events->hasGpuCompositionDoneInfo());
+ ASSERT_FALSE(events->hasDisplayPresentInfo());
+ ASSERT_FALSE(events->hasReleaseInfo());
+
+ // we should also have gotten values for the presented frame
+ events = history.getFrame(2);
+ ASSERT_NE(nullptr, events);
+ ASSERT_EQ(2, events->frameNumber);
+ ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
+ ASSERT_GE(events->postedTime, postedTimeB);
+ ASSERT_GE(events->latchTime, postedTimeB);
ASSERT_GE(events->dequeueReadyTime, events->latchTime);
ASSERT_NE(nullptr, events->gpuCompositionDoneFence);
ASSERT_NE(nullptr, events->displayPresentFence);
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index 2d50b4de74..df7739c3fb 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -21,8 +21,10 @@
#include "MockConsumer.h"
#include <gui/BufferItem.h>
+#include <gui/BufferItemConsumer.h>
#include <gui/BufferQueue.h>
#include <gui/IProducerListener.h>
+#include <gui/Surface.h>
#include <ui/GraphicBuffer.h>
@@ -35,13 +37,18 @@
#include <system/window.h>
+#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <future>
#include <thread>
+#include <com_android_graphics_libgui_flags.h>
+
using namespace std::chrono_literals;
namespace android {
+using namespace com::android::graphics::libgui;
class BufferQueueTest : public ::testing::Test {
@@ -1328,4 +1335,109 @@ TEST_F(BufferQueueTest, TestProducerConnectDisconnect) {
ASSERT_EQ(NO_INIT, mProducer->disconnect(NATIVE_WINDOW_API_CPU));
}
+TEST_F(BufferQueueTest, TestBqSetFrameRateFlagBuildTimeIsSet) {
+ ASSERT_EQ(flags::bq_setframerate(), COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE));
+}
+
+struct BufferItemConsumerSetFrameRateListener : public BufferItemConsumer {
+ BufferItemConsumerSetFrameRateListener(const sp<IGraphicBufferConsumer>& consumer)
+ : BufferItemConsumer(consumer, GRALLOC_USAGE_SW_READ_OFTEN, 1) {}
+
+ MOCK_METHOD(void, onSetFrameRate, (float, int8_t, int8_t), (override));
+};
+
+TEST_F(BufferQueueTest, TestSetFrameRate) {
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+
+ sp<BufferItemConsumerSetFrameRateListener> bufferConsumer =
+ sp<BufferItemConsumerSetFrameRateListener>::make(consumer);
+
+ EXPECT_CALL(*bufferConsumer, onSetFrameRate(12.34f, 1, 0)).Times(1);
+ producer->setFrameRate(12.34f, 1, 0);
+}
+
+class Latch {
+public:
+ explicit Latch(int expected) : mExpected(expected) {}
+ Latch(const Latch&) = delete;
+ Latch& operator=(const Latch&) = delete;
+
+ void CountDown() {
+ std::unique_lock<std::mutex> lock(mLock);
+ mExpected--;
+ if (mExpected <= 0) {
+ mCV.notify_all();
+ }
+ }
+
+ void Wait() {
+ std::unique_lock<std::mutex> lock(mLock);
+ mCV.wait(lock, [&] { return mExpected == 0; });
+ }
+
+private:
+ int mExpected;
+ std::mutex mLock;
+ std::condition_variable mCV;
+};
+
+struct OneshotOnDequeuedListener final : public BufferItemConsumer::FrameAvailableListener {
+ OneshotOnDequeuedListener(std::function<void()>&& oneshot)
+ : mOneshotRunnable(std::move(oneshot)) {}
+
+ std::function<void()> mOneshotRunnable;
+
+ void run() {
+ if (mOneshotRunnable) {
+ mOneshotRunnable();
+ mOneshotRunnable = nullptr;
+ }
+ }
+
+ void onFrameDequeued(const uint64_t) override { run(); }
+
+ void onFrameAvailable(const BufferItem&) override {}
+};
+
+// See b/270004534
+TEST(BufferQueueThreading, TestProducerDequeueConsumerDestroy) {
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+
+ sp<BufferItemConsumer> bufferConsumer =
+ sp<BufferItemConsumer>::make(consumer, GRALLOC_USAGE_SW_READ_OFTEN, 2);
+ ASSERT_NE(nullptr, bufferConsumer.get());
+ sp<Surface> surface = sp<Surface>::make(producer);
+ native_window_set_buffers_format(surface.get(), PIXEL_FORMAT_RGBA_8888);
+ native_window_set_buffers_dimensions(surface.get(), 100, 100);
+
+ Latch triggerDisconnect(1);
+ Latch resumeCallback(1);
+ auto luckyListener = sp<OneshotOnDequeuedListener>::make([&]() {
+ triggerDisconnect.CountDown();
+ resumeCallback.Wait();
+ });
+ bufferConsumer->setFrameAvailableListener(luckyListener);
+
+ std::future<void> disconnecter = std::async(std::launch::async, [&]() {
+ triggerDisconnect.Wait();
+ luckyListener = nullptr;
+ bufferConsumer = nullptr;
+ resumeCallback.CountDown();
+ });
+
+ std::future<void> render = std::async(std::launch::async, [=]() {
+ ANativeWindow_Buffer buffer;
+ surface->lock(&buffer, nullptr);
+ surface->unlockAndPost();
+ });
+
+ ASSERT_EQ(std::future_status::ready, render.wait_for(1s));
+ EXPECT_EQ(nullptr, luckyListener.get());
+ EXPECT_EQ(nullptr, bufferConsumer.get());
+}
+
} // namespace android
diff --git a/libs/gui/tests/DisplayEventStructLayout_test.cpp b/libs/gui/tests/DisplayEventStructLayout_test.cpp
index 3949d70aac..29eeaa806f 100644
--- a/libs/gui/tests/DisplayEventStructLayout_test.cpp
+++ b/libs/gui/tests/DisplayEventStructLayout_test.cpp
@@ -59,6 +59,7 @@ TEST(DisplayEventStructLayoutTest, TestEventAlignment) {
lastFrameTimelineOffset + 16);
CHECK_OFFSET(DisplayEventReceiver::Event::Hotplug, connected, 0);
+ CHECK_OFFSET(DisplayEventReceiver::Event::Hotplug, connectionError, 4);
CHECK_OFFSET(DisplayEventReceiver::Event::ModeChange, modeId, 0);
CHECK_OFFSET(DisplayEventReceiver::Event::ModeChange, vsyncPeriod, 8);
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index 4d5bd5b3fa..d4b8dbeeb9 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -24,6 +24,7 @@
#include <memory>
+#include <android/gui/BnWindowInfosReportedListener.h>
#include <android/keycodes.h>
#include <android/native_window.h>
@@ -74,6 +75,26 @@ sp<IInputFlinger> getInputFlinger() {
static const int LAYER_BASE = INT32_MAX - 10;
static constexpr std::chrono::nanoseconds DISPATCHING_TIMEOUT = 5s;
+class SynchronousWindowInfosReportedListener : public gui::BnWindowInfosReportedListener {
+public:
+ binder::Status onWindowInfosReported() override {
+ std::lock_guard<std::mutex> lock{mMutex};
+ mWindowInfosReported = true;
+ mConditionVariable.notify_one();
+ return binder::Status::ok();
+ }
+
+ void wait() {
+ std::unique_lock<std::mutex> lock{mMutex};
+ mConditionVariable.wait(lock, [&] { return mWindowInfosReported; });
+ }
+
+private:
+ std::mutex mMutex;
+ std::condition_variable mConditionVariable;
+ bool mWindowInfosReported{false};
+};
+
class InputSurface {
public:
InputSurface(const sp<SurfaceControl> &sc, int width, int height, bool noInputChannel = false) {
@@ -264,7 +285,10 @@ public:
t.setPosition(mSurfaceControl, x, y);
t.setCrop(mSurfaceControl, crop);
t.setAlpha(mSurfaceControl, 1);
- t.apply(true);
+ auto reportedListener = sp<SynchronousWindowInfosReportedListener>::make();
+ t.addWindowInfosReportedListener(reportedListener);
+ t.apply();
+ reportedListener->wait();
}
void requestFocus(int displayId = ADISPLAY_ID_DEFAULT) {
@@ -370,7 +394,7 @@ public:
// After a new buffer is queued, SurfaceFlinger is notified and will
// latch the new buffer on next vsync. Let's heuristically wait for 3
// vsyncs.
- mBufferPostDelay = static_cast<int32_t>(1e6 / mode.refreshRate) * 3;
+ mBufferPostDelay = static_cast<int32_t>(1e6 / mode.peakRefreshRate) * 3;
}
void TearDown() {
diff --git a/libs/gui/tests/FrameRateUtilsTest.cpp b/libs/gui/tests/FrameRateUtilsTest.cpp
new file mode 100644
index 0000000000..5fe22b05f9
--- /dev/null
+++ b/libs/gui/tests/FrameRateUtilsTest.cpp
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include <gui/FrameRateUtils.h>
+#include <inttypes.h>
+#include <system/window.h>
+
+#include <com_android_graphics_libgui_flags.h>
+
+namespace android {
+using namespace com::android::graphics::libgui;
+
+TEST(FrameRateUtilsTest, ValidateFrameRate) {
+ EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+ EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+ EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS, ""));
+ EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+ // Privileged APIs.
+ EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+ EXPECT_FALSE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+ constexpr bool kPrivileged = true;
+ EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "",
+ kPrivileged));
+ EXPECT_TRUE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "",
+ kPrivileged));
+
+ // Invalid frame rate.
+ EXPECT_FALSE(ValidateFrameRate(-1, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+ EXPECT_FALSE(ValidateFrameRate(1.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+ EXPECT_FALSE(ValidateFrameRate(0.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+ // Invalid compatibility.
+ EXPECT_FALSE(
+ ValidateFrameRate(60.0f, -1, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+ EXPECT_FALSE(ValidateFrameRate(60.0f, 2, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+ // Invalid change frame rate strategy.
+ if (flags::bq_setframerate()) {
+ EXPECT_FALSE(
+ ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, -1, ""));
+ EXPECT_FALSE(
+ ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, 2, ""));
+ }
+}
+
+} // namespace android
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 6adcd966c5..c6ea317949 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -208,21 +208,6 @@ protected:
ASSERT_EQ(NO_ERROR, surface->disconnect(NATIVE_WINDOW_API_CPU));
}
- static status_t captureDisplay(DisplayCaptureArgs& captureArgs,
- ScreenCaptureResults& captureResults) {
- const auto sf = ComposerServiceAIDL::getComposerService();
- SurfaceComposerClient::Transaction().apply(true);
-
- const sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener();
- binder::Status status = sf->captureDisplay(captureArgs, captureListener);
- status_t err = gui::aidl_utils::statusTFromBinderStatus(status);
- if (err != NO_ERROR) {
- return err;
- }
- captureResults = captureListener->waitForResults();
- return fenceStatus(captureResults.fenceResult);
- }
-
sp<Surface> mSurface;
sp<SurfaceComposerClient> mComposerClient;
sp<SurfaceControl> mSurfaceControl;
@@ -260,56 +245,6 @@ TEST_F(SurfaceTest, QueuesToWindowComposerIsTrueWhenPurgatorized) {
EXPECT_EQ(1, result);
}
-// This test probably doesn't belong here.
-TEST_F(SurfaceTest, ScreenshotsOfProtectedBuffersDontSucceed) {
- sp<ANativeWindow> anw(mSurface);
-
- // Verify the screenshot works with no protected buffers.
- const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
- ASSERT_FALSE(ids.empty());
- // display 0 is picked for now, can extend to support all displays if needed
- const sp<IBinder> display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
- ASSERT_FALSE(display == nullptr);
-
- DisplayCaptureArgs captureArgs;
- captureArgs.displayToken = display;
- captureArgs.width = 64;
- captureArgs.height = 64;
-
- ScreenCaptureResults captureResults;
- ASSERT_EQ(NO_ERROR, captureDisplay(captureArgs, captureResults));
-
- ASSERT_EQ(NO_ERROR, native_window_api_connect(anw.get(),
- NATIVE_WINDOW_API_CPU));
- // Set the PROTECTED usage bit and verify that the screenshot fails. Note
- // that we need to dequeue a buffer in order for it to actually get
- // allocated in SurfaceFlinger.
- ASSERT_EQ(NO_ERROR, native_window_set_usage(anw.get(),
- GRALLOC_USAGE_PROTECTED));
- ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(anw.get(), 3));
- ANativeWindowBuffer* buf = nullptr;
-
- status_t err = native_window_dequeue_buffer_and_wait(anw.get(), &buf);
- if (err) {
- // we could fail if GRALLOC_USAGE_PROTECTED is not supported.
- // that's okay as long as this is the reason for the failure.
- // try again without the GRALLOC_USAGE_PROTECTED bit.
- ASSERT_EQ(NO_ERROR, native_window_set_usage(anw.get(), 0));
- ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(anw.get(),
- &buf));
- return;
- }
- ASSERT_EQ(NO_ERROR, anw->cancelBuffer(anw.get(), buf, -1));
-
- for (int i = 0; i < 4; i++) {
- // Loop to make sure SurfaceFlinger has retired a protected buffer.
- ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(anw.get(),
- &buf));
- ASSERT_EQ(NO_ERROR, anw->queueBuffer(anw.get(), buf, -1));
- }
- ASSERT_EQ(NO_ERROR, captureDisplay(captureArgs, captureResults));
-}
-
TEST_F(SurfaceTest, ConcreteTypeIsSurface) {
sp<ANativeWindow> anw(mSurface);
int result = -123;
@@ -851,7 +786,8 @@ public:
return binder::Status::ok();
}
- binder::Status captureDisplayById(int64_t, const sp<IScreenCaptureListener>&) override {
+ binder::Status captureDisplayById(int64_t, const gui::CaptureArgs&,
+ const sp<IScreenCaptureListener>&) override {
return binder::Status::ok();
}
@@ -879,10 +815,6 @@ public:
return binder::Status::ok();
}
- binder::Status getColorManagement(bool* /*outGetColorManagement*/) override {
- return binder::Status::ok();
- }
-
binder::Status getCompositionPreference(gui::CompositionPreference* /*outPref*/) override {
return binder::Status::ok();
}
@@ -989,16 +921,34 @@ public:
return binder::Status::ok();
}
- binder::Status setOverrideFrameRate(int32_t /*uid*/, float /*frameRate*/) override {
+ binder::Status setGameModeFrameRateOverride(int32_t /*uid*/, float /*frameRate*/) override {
+ return binder::Status::ok();
+ }
+
+ binder::Status setGameDefaultFrameRateOverride(int32_t /*uid*/, float /*frameRate*/) override {
return binder::Status::ok();
}
- binder::Status updateSmallAreaDetection(const std::vector<int32_t>& /*uids*/,
+ binder::Status enableRefreshRateOverlay(bool /*active*/) override {
+ return binder::Status::ok();
+ }
+
+ binder::Status setDebugFlash(int /*delay*/) override { return binder::Status::ok(); }
+
+ binder::Status scheduleComposite() override { return binder::Status::ok(); }
+
+ binder::Status scheduleCommit() override { return binder::Status::ok(); }
+
+ binder::Status forceClientComposition(bool /*enabled*/) override {
+ return binder::Status::ok();
+ }
+
+ binder::Status updateSmallAreaDetection(const std::vector<int32_t>& /*appIds*/,
const std::vector<float>& /*thresholds*/) {
return binder::Status::ok();
}
- binder::Status setSmallAreaDetectionThreshold(int32_t /*uid*/, float /*threshold*/) {
+ binder::Status setSmallAreaDetectionThreshold(int32_t /*appId*/, float /*threshold*/) {
return binder::Status::ok();
}
@@ -1030,6 +980,10 @@ public:
return binder::Status::ok();
}
+ binder::Status getSchedulingPolicy(gui::SchedulingPolicy*) override {
+ return binder::Status::ok();
+ }
+
protected:
IBinder* onAsBinder() override { return nullptr; }
@@ -1331,7 +1285,7 @@ protected:
newFrame->mRefreshes[0].mGpuCompositionDone.mFenceTime :
FenceTime::NO_FENCE;
// HWC2 releases the previous buffer after a new latch just before
- // calling postComposition.
+ // calling onCompositionPresented.
if (oldFrame != nullptr) {
mCfeh->addRelease(nOldFrame, oldFrame->kDequeueReadyTime,
std::shared_ptr<FenceTime>(oldFrame->mRelease.mFenceTime));
diff --git a/libs/gui/tests/WindowInfo_test.cpp b/libs/gui/tests/WindowInfo_test.cpp
index 461fe4a4ce..5eb5d3bff0 100644
--- a/libs/gui/tests/WindowInfo_test.cpp
+++ b/libs/gui/tests/WindowInfo_test.cpp
@@ -28,6 +28,7 @@ namespace android {
using gui::InputApplicationInfo;
using gui::TouchOcclusionMode;
using gui::WindowInfo;
+using ui::Size;
namespace test {
@@ -52,10 +53,8 @@ TEST(WindowInfo, Parcelling) {
i.layoutParamsFlags = WindowInfo::Flag::SLIPPERY;
i.layoutParamsType = WindowInfo::Type::INPUT_METHOD;
i.dispatchingTimeout = 12s;
- i.frameLeft = 93;
- i.frameTop = 34;
- i.frameRight = 16;
- i.frameBottom = 19;
+ i.frame = Rect(93, 34, 16, 19);
+ i.contentSize = Size(10, 40);
i.surfaceInset = 17;
i.globalScaleFactor = 0.3;
i.alpha = 0.7;
@@ -85,10 +84,8 @@ TEST(WindowInfo, Parcelling) {
ASSERT_EQ(i.layoutParamsFlags, i2.layoutParamsFlags);
ASSERT_EQ(i.layoutParamsType, i2.layoutParamsType);
ASSERT_EQ(i.dispatchingTimeout, i2.dispatchingTimeout);
- ASSERT_EQ(i.frameLeft, i2.frameLeft);
- ASSERT_EQ(i.frameTop, i2.frameTop);
- ASSERT_EQ(i.frameRight, i2.frameRight);
- ASSERT_EQ(i.frameBottom, i2.frameBottom);
+ ASSERT_EQ(i.frame, i2.frame);
+ ASSERT_EQ(i.contentSize, i2.contentSize);
ASSERT_EQ(i.surfaceInset, i2.surfaceInset);
ASSERT_EQ(i.globalScaleFactor, i2.globalScaleFactor);
ASSERT_EQ(i.alpha, i2.alpha);
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index a67ed29808..b74c3b2e95 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -33,6 +33,29 @@ filegroup {
],
}
+/////////////////////////////////////////////////
+// flags
+/////////////////////////////////////////////////
+aconfig_declarations {
+ name: "com.android.input.flags-aconfig",
+ package: "com.android.input.flags",
+ srcs: ["input_flags.aconfig"],
+}
+
+cc_aconfig_library {
+ name: "com.android.input.flags-aconfig-cc",
+ aconfig_declarations: "com.android.input.flags-aconfig",
+ host_supported: true,
+ // Use the test version of the aconfig flag library by default to allow tests to set local
+ // overrides for flags, without having to link against a separate version of libinput or of this
+ // library. Bundling this library directly into libinput prevents us from having to add this
+ // library as a shared lib dependency everywhere where libinput is used.
+ mode: "test",
+ shared: {
+ enabled: false,
+ },
+}
+
aidl_interface {
name: "inputconstants",
host_supported: true,
@@ -65,12 +88,38 @@ rust_bindgen {
bindgen_flags: [
"--verbose",
"--allowlist-var=AMOTION_EVENT_FLAG_CANCELED",
+ "--allowlist-var=AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED",
+ "--allowlist-var=AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED",
+ "--allowlist-var=AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT",
+ "--allowlist-var=AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE",
"--allowlist-var=AMOTION_EVENT_ACTION_CANCEL",
"--allowlist-var=AMOTION_EVENT_ACTION_UP",
"--allowlist-var=AMOTION_EVENT_ACTION_POINTER_DOWN",
"--allowlist-var=AMOTION_EVENT_ACTION_DOWN",
"--allowlist-var=AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT",
"--allowlist-var=MAX_POINTER_ID",
+ "--allowlist-var=AINPUT_SOURCE_CLASS_NONE",
+ "--allowlist-var=AINPUT_SOURCE_CLASS_BUTTON",
+ "--allowlist-var=AINPUT_SOURCE_CLASS_POINTER",
+ "--allowlist-var=AINPUT_SOURCE_CLASS_NAVIGATION",
+ "--allowlist-var=AINPUT_SOURCE_CLASS_POSITION",
+ "--allowlist-var=AINPUT_SOURCE_CLASS_JOYSTICK",
+ "--allowlist-var=AINPUT_SOURCE_UNKNOWN",
+ "--allowlist-var=AINPUT_SOURCE_KEYBOARD",
+ "--allowlist-var=AINPUT_SOURCE_DPAD",
+ "--allowlist-var=AINPUT_SOURCE_GAMEPAD",
+ "--allowlist-var=AINPUT_SOURCE_TOUCHSCREEN",
+ "--allowlist-var=AINPUT_SOURCE_MOUSE",
+ "--allowlist-var=AINPUT_SOURCE_STYLUS",
+ "--allowlist-var=AINPUT_SOURCE_BLUETOOTH_STYLUS",
+ "--allowlist-var=AINPUT_SOURCE_TRACKBALL",
+ "--allowlist-var=AINPUT_SOURCE_MOUSE_RELATIVE",
+ "--allowlist-var=AINPUT_SOURCE_TOUCHPAD",
+ "--allowlist-var=AINPUT_SOURCE_TOUCH_NAVIGATION",
+ "--allowlist-var=AINPUT_SOURCE_JOYSTICK",
+ "--allowlist-var=AINPUT_SOURCE_HDMI",
+ "--allowlist-var=AINPUT_SOURCE_SENSOR",
+ "--allowlist-var=AINPUT_SOURCE_ROTARY_ENCODER",
],
static_libs: [
@@ -109,62 +158,6 @@ cc_library_static {
],
}
-genrule {
- name: "libinput_cxx_bridge_code",
- tools: ["cxxbridge"],
- cmd: "$(location cxxbridge) $(in) >> $(out)",
- srcs: ["input_verifier.rs"],
- out: ["inputverifier_generated.cpp"],
-}
-
-genrule {
- name: "libinput_cxx_bridge_header",
- tools: ["cxxbridge"],
- cmd: "$(location cxxbridge) $(in) --header >> $(out)",
- srcs: ["input_verifier.rs"],
- out: ["input_verifier.rs.h"],
-}
-
-rust_defaults {
- name: "libinput_rust_defaults",
- srcs: ["input_verifier.rs"],
- host_supported: true,
- rustlibs: [
- "libbitflags",
- "libcxx",
- "libinput_bindgen",
- "liblogger",
- "liblog_rust",
- "inputconstants-rust",
- ],
-
- shared_libs: [
- "libbase",
- "liblog",
- ],
-}
-
-rust_ffi_static {
- name: "libinput_rust",
- crate_name: "input",
- defaults: ["libinput_rust_defaults"],
-}
-
-rust_test {
- name: "libinput_rust_test",
- defaults: ["libinput_rust_defaults"],
- whole_static_libs: [
- "libinput_from_rust_to_cpp",
- ],
- test_options: {
- unit_test: true,
- },
- test_suites: ["device_tests"],
- sanitize: {
- hwaddress: true,
- },
-}
-
cc_library {
name: "libinput",
cpp_std: "c++20",
@@ -174,12 +167,18 @@ cc_library {
"-Wextra",
"-Werror",
"-Wno-unused-parameter",
+ "-Wthread-safety",
+ "-Wshadow",
+ "-Wshadow-field-in-constructor-modified",
+ "-Wshadow-uncaptured-local",
+ "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
],
srcs: [
- "FromRustToCpp.cpp",
+ "android/os/IInputFlinger.aidl",
"Input.cpp",
"InputDevice.cpp",
"InputEventLabels.cpp",
+ "InputTransport.cpp",
"InputVerifier.cpp",
"Keyboard.cpp",
"KeyCharacterMap.cpp",
@@ -213,15 +212,16 @@ cc_library {
"toolbox_input_labels",
],
- generated_sources: ["libinput_cxx_bridge_code"],
-
shared_libs: [
"libbase",
+ "libbinder",
"libcutils",
"liblog",
"libPlatformProperties",
"libtinyxml2",
+ "libutils",
"libz", // needed by libkernelconfigs
+ "server_configurable_flags",
],
ldflags: [
@@ -236,16 +236,19 @@ cc_library {
static_libs: [
"inputconstants-cpp",
+ "libgui_window_info_static",
"libui-types",
"libtflite_static",
"libkernelconfigs",
],
whole_static_libs: [
- "libinput_rust",
+ "com.android.input.flags-aconfig-cc",
+ "libinput_rust_ffi",
],
export_static_lib_headers: [
+ "libgui_window_info_static",
"libui-types",
],
@@ -256,11 +259,6 @@ cc_library {
target: {
android: {
- srcs: [
- "InputTransport.cpp",
- "android/os/IInputFlinger.aidl",
- ],
-
export_shared_lib_headers: ["libbinder"],
shared_libs: [
@@ -272,42 +270,15 @@ cc_library {
"android.os.statsbootstrap_aidl-cpp",
],
- static_libs: [
- "libgui_window_info_static",
- ],
-
- export_static_lib_headers: [
- "libgui_window_info_static",
- ],
-
required: [
"motion_predictor_model_prebuilt",
"motion_predictor_model_config",
],
},
host: {
- shared: {
- enabled: false,
- },
include_dirs: [
"bionic/libc/kernel/android/uapi/",
"bionic/libc/kernel/uapi",
- "frameworks/native/libs/arect/include",
- ],
- },
- host_linux: {
- srcs: [
- "InputTransport.cpp",
- ],
- static_libs: [
- "libgui_window_info_static",
- ],
- shared_libs: [
- "libbinder",
- ],
-
- export_static_lib_headers: [
- "libgui_window_info_static",
],
},
},
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 00925ba555..bd5b67b1d0 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -217,6 +217,19 @@ bool isStylusToolType(ToolType toolType) {
return toolType == ToolType::STYLUS || toolType == ToolType::ERASER;
}
+bool isStylusEvent(uint32_t source, const std::vector<PointerProperties>& properties) {
+ if (!isFromSource(source, AINPUT_SOURCE_STYLUS)) {
+ return false;
+ }
+ // Need at least one stylus pointer for this event to be considered a stylus event
+ for (const PointerProperties& pointerProperties : properties) {
+ if (isStylusToolType(pointerProperties.toolType)) {
+ return true;
+ }
+ }
+ return false;
+}
+
VerifiedKeyEvent verifiedKeyEventFromKeyEvent(const KeyEvent& event) {
return {{VerifiedInputEvent::Type::KEY, event.getDeviceId(), event.getEventTime(),
event.getSource(), event.getDisplayId()},
@@ -497,19 +510,6 @@ void PointerCoords::transform(const ui::Transform& transform) {
}
}
-// --- PointerProperties ---
-
-bool PointerProperties::operator==(const PointerProperties& other) const {
- return id == other.id
- && toolType == other.toolType;
-}
-
-void PointerProperties::copyFrom(const PointerProperties& other) {
- id = other.id;
- toolType = other.toolType;
-}
-
-
// --- MotionEvent ---
void MotionEvent::initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId,
diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp
index b25e06c869..0e627e56fd 100644
--- a/libs/input/InputEventLabels.cpp
+++ b/libs/input/InputEventLabels.cpp
@@ -18,6 +18,7 @@
#include <linux/input-event-codes.h>
#include <linux/input.h>
+#include <strings.h>
#define DEFINE_KEYCODE(key) { #key, AKEYCODE_##key }
#define DEFINE_AXIS(axis) { #axis, AMOTION_EVENT_AXIS_##axis }
@@ -524,6 +525,14 @@ std::string getLabel(const label* labels, int value) {
return labels->name != nullptr ? labels->name : std::to_string(value);
}
+std::optional<int> getValue(const label* labels, const char* searchLabel) {
+ if (labels == nullptr) return {};
+ while (labels->name != nullptr && ::strcasecmp(labels->name, searchLabel) != 0) {
+ labels++;
+ }
+ return labels->name != nullptr ? std::make_optional(labels->value) : std::nullopt;
+}
+
const label* getCodeLabelsForType(int32_t type) {
switch (type) {
case EV_SYN:
@@ -557,7 +566,7 @@ const label* getValueLabelsForTypeAndCode(int32_t type, int32_t code) {
if (type == EV_KEY) {
return ev_key_value_labels;
}
- if (type == EV_MSC && code == ABS_MT_TOOL_TYPE) {
+ if (type == EV_ABS && code == ABS_MT_TOOL_TYPE) {
return mt_tool_labels;
}
return nullptr;
@@ -573,4 +582,17 @@ EvdevEventLabel InputEventLookup::getLinuxEvdevLabel(int32_t type, int32_t code,
};
}
+std::optional<int> InputEventLookup::getLinuxEvdevEventTypeByLabel(const char* label) {
+ return getValue(ev_labels, label);
+}
+
+std::optional<int> InputEventLookup::getLinuxEvdevEventCodeByLabel(int32_t type,
+ const char* label) {
+ return getValue(getCodeLabelsForType(type), label);
+}
+
+std::optional<int> InputEventLookup::getLinuxEvdevInputPropByLabel(const char* label) {
+ return getValue(input_prop_labels, label);
+}
+
} // namespace android
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 01d9ebbc71..09e98d0515 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -23,7 +23,11 @@
#include <log/log.h>
#include <utils/Trace.h>
+#include <com_android_input_flags.h>
#include <input/InputTransport.h>
+#include <input/TraceTools.h>
+
+namespace input_flags = com::android::input::flags;
namespace {
@@ -75,10 +79,20 @@ bool debugTransportPublisher() {
/**
* Log debug messages about touch event resampling.
- * Enable this via "adb shell setprop log.tag.InputTransportResampling DEBUG" (requires restart)
+ *
+ * Enable this via "adb shell setprop log.tag.InputTransportResampling DEBUG".
+ * This requires a restart on non-debuggable (e.g. user) builds, but should take effect immediately
+ * on debuggable builds (e.g. userdebug).
*/
-const bool DEBUG_RESAMPLING =
- __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO);
+bool debugResampling() {
+ if (!IS_DEBUGGABLE_BUILD) {
+ static const bool DEBUG_TRANSPORT_RESAMPLING =
+ __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling",
+ ANDROID_LOG_INFO);
+ return DEBUG_TRANSPORT_RESAMPLING;
+ }
+ return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO);
+}
} // namespace
@@ -128,7 +142,8 @@ static const char* PROPERTY_RESAMPLING_ENABLED = "ro.input.resampling";
* Enable this via "adb shell setprop log.tag.InputTransportVerifyEvents DEBUG"
*/
static bool verifyEvents() {
- return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "VerifyEvents", ANDROID_LOG_INFO);
+ return input_flags::enable_outbound_event_verification() ||
+ __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "VerifyEvents", ANDROID_LOG_INFO);
}
template<typename T>
@@ -409,7 +424,7 @@ status_t InputChannel::openInputChannelPair(const std::string& name,
setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
- sp<IBinder> token = new BBinder();
+ sp<IBinder> token = sp<BBinder>::make();
std::string serverChannelName = name + " (server)";
android::base::unique_fd serverFd(sockets[0]);
@@ -422,6 +437,10 @@ status_t InputChannel::openInputChannelPair(const std::string& name,
}
status_t InputChannel::sendMessage(const InputMessage* msg) {
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("sendMessage(inputChannel=%s, seq=0x%" PRIx32 ", type=0x%" PRIx32
+ ")",
+ mName.c_str(), msg->header.seq, msg->header.type));
const size_t msgLength = msg->size();
InputMessage cleanMsg;
msg->getSanitizedCopy(&cleanMsg);
@@ -453,12 +472,6 @@ status_t InputChannel::sendMessage(const InputMessage* msg) {
ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ sent message of type %s", mName.c_str(),
ftl::enum_string(msg->header.type).c_str());
- if (ATRACE_ENABLED()) {
- std::string message =
- StringPrintf("sendMessage(inputChannel=%s, seq=0x%" PRIx32 ", type=0x%" PRIx32 ")",
- mName.c_str(), msg->header.seq, msg->header.type);
- ATRACE_NAME(message.c_str());
- }
return OK;
}
@@ -494,8 +507,8 @@ status_t InputChannel::receiveMessage(InputMessage* msg) {
ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ received message of type %s", mName.c_str(),
ftl::enum_string(msg->header.type).c_str());
-
if (ATRACE_ENABLED()) {
+ // Add an additional trace point to include data about the received message.
std::string message = StringPrintf("receiveMessage(inputChannel=%s, seq=0x%" PRIx32
", type=0x%" PRIx32 ")",
mName.c_str(), msg->header.seq, msg->header.type);
@@ -568,13 +581,10 @@ status_t InputPublisher::publishKeyEvent(uint32_t seq, int32_t eventId, int32_t
int32_t flags, int32_t keyCode, int32_t scanCode,
int32_t metaState, int32_t repeatCount, nsecs_t downTime,
nsecs_t eventTime) {
- if (ATRACE_ENABLED()) {
- std::string message =
- StringPrintf("publishKeyEvent(inputChannel=%s, action=%s, keyCode=%s)",
- mChannel->getName().c_str(), KeyEvent::actionToString(action),
- KeyEvent::getLabel(keyCode));
- ATRACE_NAME(message.c_str());
- }
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("publishKeyEvent(inputChannel=%s, action=%s, keyCode=%s)",
+ mChannel->getName().c_str(), KeyEvent::actionToString(action),
+ KeyEvent::getLabel(keyCode)));
ALOGD_IF(debugTransportPublisher(),
"channel '%s' publisher ~ %s: seq=%u, id=%d, deviceId=%d, source=%s, "
"action=%s, flags=0x%x, keyCode=%s, scanCode=%d, metaState=0x%x, repeatCount=%d,"
@@ -616,16 +626,14 @@ status_t InputPublisher::publishMotionEvent(
const ui::Transform& rawTransform, nsecs_t downTime, nsecs_t eventTime,
uint32_t pointerCount, const PointerProperties* pointerProperties,
const PointerCoords* pointerCoords) {
- if (ATRACE_ENABLED()) {
- std::string message = StringPrintf("publishMotionEvent(inputChannel=%s, action=%s)",
- mChannel->getName().c_str(),
- MotionEvent::actionToString(action).c_str());
- ATRACE_NAME(message.c_str());
- }
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("publishMotionEvent(inputChannel=%s, action=%s)",
+ mChannel->getName().c_str(),
+ MotionEvent::actionToString(action).c_str()));
if (verifyEvents()) {
Result<void> result =
- mInputVerifier.processMovement(deviceId, action, pointerCount, pointerProperties,
- pointerCoords, flags);
+ mInputVerifier.processMovement(deviceId, source, action, pointerCount,
+ pointerProperties, pointerCoords, flags);
if (!result.ok()) {
LOG(FATAL) << "Bad stream: " << result.error();
}
@@ -692,19 +700,17 @@ status_t InputPublisher::publishMotionEvent(
msg.body.motion.eventTime = eventTime;
msg.body.motion.pointerCount = pointerCount;
for (uint32_t i = 0; i < pointerCount; i++) {
- msg.body.motion.pointers[i].properties.copyFrom(pointerProperties[i]);
- msg.body.motion.pointers[i].coords.copyFrom(pointerCoords[i]);
+ msg.body.motion.pointers[i].properties = pointerProperties[i];
+ msg.body.motion.pointers[i].coords = pointerCoords[i];
}
return mChannel->sendMessage(&msg);
}
status_t InputPublisher::publishFocusEvent(uint32_t seq, int32_t eventId, bool hasFocus) {
- if (ATRACE_ENABLED()) {
- std::string message = StringPrintf("publishFocusEvent(inputChannel=%s, hasFocus=%s)",
- mChannel->getName().c_str(), toString(hasFocus));
- ATRACE_NAME(message.c_str());
- }
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("publishFocusEvent(inputChannel=%s, hasFocus=%s)",
+ mChannel->getName().c_str(), toString(hasFocus)));
ALOGD_IF(debugTransportPublisher(), "channel '%s' publisher ~ %s: seq=%u, id=%d, hasFocus=%s",
mChannel->getName().c_str(), __func__, seq, eventId, toString(hasFocus));
@@ -718,12 +724,9 @@ status_t InputPublisher::publishFocusEvent(uint32_t seq, int32_t eventId, bool h
status_t InputPublisher::publishCaptureEvent(uint32_t seq, int32_t eventId,
bool pointerCaptureEnabled) {
- if (ATRACE_ENABLED()) {
- std::string message =
- StringPrintf("publishCaptureEvent(inputChannel=%s, pointerCaptureEnabled=%s)",
- mChannel->getName().c_str(), toString(pointerCaptureEnabled));
- ATRACE_NAME(message.c_str());
- }
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("publishCaptureEvent(inputChannel=%s, pointerCaptureEnabled=%s)",
+ mChannel->getName().c_str(), toString(pointerCaptureEnabled)));
ALOGD_IF(debugTransportPublisher(),
"channel '%s' publisher ~ %s: seq=%u, id=%d, pointerCaptureEnabled=%s",
mChannel->getName().c_str(), __func__, seq, eventId, toString(pointerCaptureEnabled));
@@ -738,12 +741,9 @@ status_t InputPublisher::publishCaptureEvent(uint32_t seq, int32_t eventId,
status_t InputPublisher::publishDragEvent(uint32_t seq, int32_t eventId, float x, float y,
bool isExiting) {
- if (ATRACE_ENABLED()) {
- std::string message =
- StringPrintf("publishDragEvent(inputChannel=%s, x=%f, y=%f, isExiting=%s)",
- mChannel->getName().c_str(), x, y, toString(isExiting));
- ATRACE_NAME(message.c_str());
- }
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("publishDragEvent(inputChannel=%s, x=%f, y=%f, isExiting=%s)",
+ mChannel->getName().c_str(), x, y, toString(isExiting)));
ALOGD_IF(debugTransportPublisher(),
"channel '%s' publisher ~ %s: seq=%u, id=%d, x=%f, y=%f, isExiting=%s",
mChannel->getName().c_str(), __func__, seq, eventId, x, y, toString(isExiting));
@@ -759,12 +759,9 @@ status_t InputPublisher::publishDragEvent(uint32_t seq, int32_t eventId, float x
}
status_t InputPublisher::publishTouchModeEvent(uint32_t seq, int32_t eventId, bool isInTouchMode) {
- if (ATRACE_ENABLED()) {
- std::string message =
- StringPrintf("publishTouchModeEvent(inputChannel=%s, isInTouchMode=%s)",
- mChannel->getName().c_str(), toString(isInTouchMode));
- ATRACE_NAME(message.c_str());
- }
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("publishTouchModeEvent(inputChannel=%s, isInTouchMode=%s)",
+ mChannel->getName().c_str(), toString(isInTouchMode)));
ALOGD_IF(debugTransportPublisher(),
"channel '%s' publisher ~ %s: seq=%u, id=%d, isInTouchMode=%s",
mChannel->getName().c_str(), __func__, seq, eventId, toString(isInTouchMode));
@@ -781,8 +778,10 @@ android::base::Result<InputPublisher::ConsumerResponse> InputPublisher::receiveC
InputMessage msg;
status_t result = mChannel->receiveMessage(&msg);
if (result) {
- ALOGD_IF(debugTransportPublisher(), "channel '%s' publisher ~ %s: %s",
- mChannel->getName().c_str(), __func__, strerror(result));
+ if (debugTransportPublisher() && result != WOULD_BLOCK) {
+ LOG(INFO) << "channel '" << mChannel->getName() << "' publisher ~ " << __func__ << ": "
+ << strerror(result);
+ }
return android::base::Error(result);
}
if (msg.header.type == InputMessage::Type::FINISHED) {
@@ -1158,7 +1157,7 @@ void InputConsumer::rewriteMessage(TouchState& state, InputMessage& msg) {
state.recentCoordinatesAreIdentical(id)) {
PointerCoords& msgCoords = msg.body.motion.pointers[i].coords;
const PointerCoords& resampleCoords = state.lastResample.getPointerById(id);
- ALOGD_IF(DEBUG_RESAMPLING, "[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id,
+ ALOGD_IF(debugResampling(), "[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id,
resampleCoords.getX(), resampleCoords.getY(), msgCoords.getX(),
msgCoords.getY());
msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX());
@@ -1181,13 +1180,13 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
ssize_t index = findTouchState(event->getDeviceId(), event->getSource());
if (index < 0) {
- ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, no touch state for device.");
+ ALOGD_IF(debugResampling(), "Not resampled, no touch state for device.");
return;
}
TouchState& touchState = mTouchStates[index];
if (touchState.historySize < 1) {
- ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, no history for device.");
+ ALOGD_IF(debugResampling(), "Not resampled, no history for device.");
return;
}
@@ -1197,7 +1196,7 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
for (size_t i = 0; i < pointerCount; i++) {
uint32_t id = event->getPointerId(i);
if (!current->idBits.hasBit(id)) {
- ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, missing id %d", id);
+ ALOGD_IF(debugResampling(), "Not resampled, missing id %d", id);
return;
}
}
@@ -1213,7 +1212,7 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
other = &future;
nsecs_t delta = future.eventTime - current->eventTime;
if (delta < RESAMPLE_MIN_DELTA) {
- ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, delta time is too small: %" PRId64 " ns.",
+ ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.",
delta);
return;
}
@@ -1224,17 +1223,17 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
other = touchState.getHistory(1);
nsecs_t delta = current->eventTime - other->eventTime;
if (delta < RESAMPLE_MIN_DELTA) {
- ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, delta time is too small: %" PRId64 " ns.",
+ ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.",
delta);
return;
} else if (delta > RESAMPLE_MAX_DELTA) {
- ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, delta time is too large: %" PRId64 " ns.",
+ ALOGD_IF(debugResampling(), "Not resampled, delta time is too large: %" PRId64 " ns.",
delta);
return;
}
nsecs_t maxPredict = current->eventTime + min(delta / 2, RESAMPLE_MAX_PREDICTION);
if (sampleTime > maxPredict) {
- ALOGD_IF(DEBUG_RESAMPLING,
+ ALOGD_IF(debugResampling(),
"Sample time is too far in the future, adjusting prediction "
"from %" PRId64 " to %" PRId64 " ns.",
sampleTime - current->eventTime, maxPredict - current->eventTime);
@@ -1242,7 +1241,7 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
}
alpha = float(current->eventTime - sampleTime) / delta;
} else {
- ALOGD_IF(DEBUG_RESAMPLING, "Not resampled, insufficient data.");
+ ALOGD_IF(debugResampling(), "Not resampled, insufficient data.");
return;
}
@@ -1270,27 +1269,27 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
// We know here that the coordinates for the pointer haven't changed because we
// would've cleared the resampled bit in rewriteMessage if they had. We can't modify
// lastResample in place becasue the mapping from pointer ID to index may have changed.
- touchState.lastResample.pointers[i].copyFrom(oldLastResample.getPointerById(id));
+ touchState.lastResample.pointers[i] = oldLastResample.getPointerById(id);
continue;
}
PointerCoords& resampledCoords = touchState.lastResample.pointers[i];
const PointerCoords& currentCoords = current->getPointerById(id);
- resampledCoords.copyFrom(currentCoords);
+ resampledCoords = currentCoords;
+ resampledCoords.isResampled = true;
if (other->idBits.hasBit(id) && shouldResampleTool(event->getToolType(i))) {
const PointerCoords& otherCoords = other->getPointerById(id);
resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X,
lerp(currentCoords.getX(), otherCoords.getX(), alpha));
resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y,
lerp(currentCoords.getY(), otherCoords.getY(), alpha));
- resampledCoords.isResampled = true;
- ALOGD_IF(DEBUG_RESAMPLING,
+ ALOGD_IF(debugResampling(),
"[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
"other (%0.3f, %0.3f), alpha %0.3f",
id, resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
currentCoords.getY(), otherCoords.getX(), otherCoords.getY(), alpha);
} else {
- ALOGD_IF(DEBUG_RESAMPLING, "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)", id,
+ ALOGD_IF(debugResampling(), "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)", id,
resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
currentCoords.getY());
}
@@ -1454,8 +1453,8 @@ void InputConsumer::initializeMotionEvent(MotionEvent* event, const InputMessage
PointerProperties pointerProperties[pointerCount];
PointerCoords pointerCoords[pointerCount];
for (uint32_t i = 0; i < pointerCount; i++) {
- pointerProperties[i].copyFrom(msg->body.motion.pointers[i].properties);
- pointerCoords[i].copyFrom(msg->body.motion.pointers[i].coords);
+ pointerProperties[i] = msg->body.motion.pointers[i].properties;
+ pointerCoords[i] = msg->body.motion.pointers[i].coords;
}
ui::Transform transform;
@@ -1484,7 +1483,7 @@ void InputConsumer::addSample(MotionEvent* event, const InputMessage* msg) {
uint32_t pointerCount = msg->body.motion.pointerCount;
PointerCoords pointerCoords[pointerCount];
for (uint32_t i = 0; i < pointerCount; i++) {
- pointerCoords[i].copyFrom(msg->body.motion.pointers[i].coords);
+ pointerCoords[i] = msg->body.motion.pointers[i].coords;
}
event->setMetaState(event->getMetaState() | msg->body.motion.metaState);
diff --git a/libs/input/InputVerifier.cpp b/libs/input/InputVerifier.cpp
index 9745e89234..cec244539e 100644
--- a/libs/input/InputVerifier.cpp
+++ b/libs/input/InputVerifier.cpp
@@ -18,12 +18,14 @@
#include <android-base/logging.h>
#include <input/InputVerifier.h>
-#include "input_verifier.rs.h"
+#include "input_cxx_bridge.rs.h"
using android::base::Error;
using android::base::Result;
using android::input::RustPointerProperties;
+using DeviceId = int32_t;
+
namespace android {
// --- InputVerifier ---
@@ -31,7 +33,8 @@ namespace android {
InputVerifier::InputVerifier(const std::string& name)
: mVerifier(android::input::verifier::create(rust::String::lossy(name))){};
-Result<void> InputVerifier::processMovement(int32_t deviceId, int32_t action, uint32_t pointerCount,
+Result<void> InputVerifier::processMovement(DeviceId deviceId, int32_t source, int32_t action,
+ uint32_t pointerCount,
const PointerProperties* pointerProperties,
const PointerCoords* pointerCoords, int32_t flags) {
std::vector<RustPointerProperties> rpp;
@@ -40,8 +43,8 @@ Result<void> InputVerifier::processMovement(int32_t deviceId, int32_t action, ui
}
rust::Slice<const RustPointerProperties> properties{rpp.data(), rpp.size()};
rust::String errorMessage =
- android::input::verifier::process_movement(*mVerifier, deviceId, action, properties,
- flags);
+ android::input::verifier::process_movement(*mVerifier, deviceId, source, action,
+ properties, static_cast<uint32_t>(flags));
if (errorMessage.empty()) {
return {};
} else {
@@ -49,4 +52,8 @@ Result<void> InputVerifier::processMovement(int32_t deviceId, int32_t action, ui
}
}
+void InputVerifier::resetDevice(DeviceId deviceId) {
+ android::input::verifier::reset_device(*mVerifier, deviceId);
+}
+
} // namespace android
diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp
index a4cd239a92..e2feabcbbe 100644
--- a/libs/input/KeyCharacterMap.cpp
+++ b/libs/input/KeyCharacterMap.cpp
@@ -613,14 +613,14 @@ void KeyCharacterMap::addLockedMetaKey(Vector<KeyEvent>& outEvents,
}
#ifdef __linux__
-std::shared_ptr<KeyCharacterMap> KeyCharacterMap::readFromParcel(Parcel* parcel) {
+std::unique_ptr<KeyCharacterMap> KeyCharacterMap::readFromParcel(Parcel* parcel) {
if (parcel == nullptr) {
ALOGE("%s: Null parcel", __func__);
return nullptr;
}
std::string loadFileName = parcel->readCString();
- std::shared_ptr<KeyCharacterMap> map =
- std::shared_ptr<KeyCharacterMap>(new KeyCharacterMap(loadFileName));
+ std::unique_ptr<KeyCharacterMap> map =
+ std::make_unique<KeyCharacterMap>(KeyCharacterMap(loadFileName));
map->mType = static_cast<KeyCharacterMap::KeyboardType>(parcel->readInt32());
map->mLayoutOverlayApplied = parcel->readBool();
size_t numKeys = parcel->readInt32();
diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
index 5736ad7eed..c4e3ff6dee 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -25,9 +25,9 @@
#include <string>
#include <vector>
+#include <android-base/logging.h>
#include <android-base/strings.h>
#include <android/input.h>
-#include <log/log.h>
#include <attestation/HmacKeyManager.h>
#include <ftl/enum.h>
@@ -60,9 +60,11 @@ TfLiteMotionPredictorSample::Point convertPrediction(
// --- MotionPredictor ---
MotionPredictor::MotionPredictor(nsecs_t predictionTimestampOffsetNanos,
- std::function<bool()> checkMotionPredictionEnabled)
+ std::function<bool()> checkMotionPredictionEnabled,
+ ReportAtomFunction reportAtomFunction)
: mPredictionTimestampOffsetNanos(predictionTimestampOffsetNanos),
- mCheckMotionPredictionEnabled(std::move(checkMotionPredictionEnabled)) {}
+ mCheckMotionPredictionEnabled(std::move(checkMotionPredictionEnabled)),
+ mReportAtomFunction(reportAtomFunction) {}
android::base::Result<void> MotionPredictor::record(const MotionEvent& event) {
if (mLastEvent && mLastEvent->getDeviceId() != event.getDeviceId()) {
@@ -90,6 +92,13 @@ android::base::Result<void> MotionPredictor::record(const MotionEvent& event) {
mBuffers = std::make_unique<TfLiteMotionPredictorBuffers>(mModel->inputLength());
}
+ // Pass input event to the MetricsManager.
+ if (!mMetricsManager) {
+ mMetricsManager.emplace(mModel->config().predictionInterval, mModel->outputLength(),
+ mReportAtomFunction);
+ }
+ mMetricsManager->onRecord(event);
+
const int32_t action = event.getActionMasked();
if (action == AMOTION_EVENT_ACTION_UP || action == AMOTION_EVENT_ACTION_CANCEL) {
ALOGD_IF(isDebug(), "End of event stream");
@@ -135,12 +144,6 @@ android::base::Result<void> MotionPredictor::record(const MotionEvent& event) {
}
mLastEvent->copyFrom(&event, /*keepHistory=*/false);
- // Pass input event to the MetricsManager.
- if (!mMetricsManager) {
- mMetricsManager.emplace(mModel->config().predictionInterval, mModel->outputLength());
- }
- mMetricsManager->onRecord(event);
-
return {};
}
@@ -205,6 +208,13 @@ std::unique_ptr<MotionEvent> MotionPredictor::predict(nsecs_t timestamp) {
coords.setAxisValue(AMOTION_EVENT_AXIS_X, predictedPoint.x);
coords.setAxisValue(AMOTION_EVENT_AXIS_Y, predictedPoint.y);
coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, predictedPressure[i]);
+ // Copy forward tilt and orientation from the last event until they are predicted
+ // (b/291789258).
+ coords.setAxisValue(AMOTION_EVENT_AXIS_TILT,
+ event.getAxisValue(AMOTION_EVENT_AXIS_TILT, 0));
+ coords.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION,
+ event.getRawPointerCoords(0)->getAxisValue(
+ AMOTION_EVENT_AXIS_ORIENTATION));
predictionTime += mModel->config().predictionInterval;
if (i == 0) {
diff --git a/libs/input/MotionPredictorMetricsManager.cpp b/libs/input/MotionPredictorMetricsManager.cpp
index 67b103290f..0412d08181 100644
--- a/libs/input/MotionPredictorMetricsManager.cpp
+++ b/libs/input/MotionPredictorMetricsManager.cpp
@@ -46,13 +46,36 @@ inline constexpr float PATH_LENGTH_EPSILON = 0.001;
} // namespace
-MotionPredictorMetricsManager::MotionPredictorMetricsManager(nsecs_t predictionInterval,
- size_t maxNumPredictions)
+void MotionPredictorMetricsManager::defaultReportAtomFunction(
+ const MotionPredictorMetricsManager::AtomFields& atomFields) {
+ // Call stats_write logging function only on Android targets (not supported on host).
+#ifdef __ANDROID__
+ android::stats::libinput::
+ stats_write(android::stats::libinput::STYLUS_PREDICTION_METRICS_REPORTED,
+ /*stylus_vendor_id=*/0,
+ /*stylus_product_id=*/0,
+ atomFields.deltaTimeBucketMilliseconds,
+ atomFields.alongTrajectoryErrorMeanMillipixels,
+ atomFields.alongTrajectoryErrorStdMillipixels,
+ atomFields.offTrajectoryRmseMillipixels,
+ atomFields.pressureRmseMilliunits,
+ atomFields.highVelocityAlongTrajectoryRmse,
+ atomFields.highVelocityOffTrajectoryRmse,
+ atomFields.scaleInvariantAlongTrajectoryRmse,
+ atomFields.scaleInvariantOffTrajectoryRmse);
+#endif
+}
+
+MotionPredictorMetricsManager::MotionPredictorMetricsManager(
+ nsecs_t predictionInterval,
+ size_t maxNumPredictions,
+ ReportAtomFunction reportAtomFunction)
: mPredictionInterval(predictionInterval),
mMaxNumPredictions(maxNumPredictions),
mRecentGroundTruthPoints(maxNumPredictions + 1),
mAggregatedMetrics(maxNumPredictions),
- mAtomFields(maxNumPredictions) {}
+ mAtomFields(maxNumPredictions),
+ mReportAtomFunction(reportAtomFunction ? reportAtomFunction : defaultReportAtomFunction) {}
void MotionPredictorMetricsManager::onRecord(const MotionEvent& inputEvent) {
// Convert MotionEvent to GroundTruthPoint.
@@ -81,8 +104,8 @@ void MotionPredictorMetricsManager::onRecord(const MotionEvent& inputEvent) {
if (mRecentGroundTruthPoints.size() >= 2) {
computeAtomFields();
reportMetrics();
- break;
}
+ break;
}
}
}
@@ -345,28 +368,10 @@ void MotionPredictorMetricsManager::computeAtomFields() {
}
void MotionPredictorMetricsManager::reportMetrics() {
- // Report one atom for each time bucket.
+ LOG_ALWAYS_FATAL_IF(!mReportAtomFunction);
+ // Report one atom for each prediction time bucket.
for (size_t i = 0; i < mAtomFields.size(); ++i) {
- // Call stats_write logging function only on Android targets (not supported on host).
-#ifdef __ANDROID__
- android::stats::libinput::
- stats_write(android::stats::libinput::STYLUS_PREDICTION_METRICS_REPORTED,
- /*stylus_vendor_id=*/0,
- /*stylus_product_id=*/0, mAtomFields[i].deltaTimeBucketMilliseconds,
- mAtomFields[i].alongTrajectoryErrorMeanMillipixels,
- mAtomFields[i].alongTrajectoryErrorStdMillipixels,
- mAtomFields[i].offTrajectoryRmseMillipixels,
- mAtomFields[i].pressureRmseMilliunits,
- mAtomFields[i].highVelocityAlongTrajectoryRmse,
- mAtomFields[i].highVelocityOffTrajectoryRmse,
- mAtomFields[i].scaleInvariantAlongTrajectoryRmse,
- mAtomFields[i].scaleInvariantOffTrajectoryRmse);
-#endif
- }
-
- // Set mock atom fields, if available.
- if (mMockLoggedAtomFields != nullptr) {
- *mMockLoggedAtomFields = mAtomFields;
+ mReportAtomFunction(mAtomFields[i]);
}
}
diff --git a/libs/input/VelocityControl.cpp b/libs/input/VelocityControl.cpp
index 5720099033..c835a081a5 100644
--- a/libs/input/VelocityControl.cpp
+++ b/libs/input/VelocityControl.cpp
@@ -37,7 +37,7 @@ VelocityControl::VelocityControl() {
reset();
}
-VelocityControlParameters& VelocityControl::getParameters() {
+const VelocityControlParameters& VelocityControl::getParameters() const{
return mParameters;
}
diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp
index 078109a5b6..613a0df040 100644
--- a/libs/input/VelocityTracker.cpp
+++ b/libs/input/VelocityTracker.cpp
@@ -17,14 +17,13 @@
#define LOG_TAG "VelocityTracker"
#include <android-base/logging.h>
-#include <array>
#include <ftl/enum.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h>
+#include <array>
#include <optional>
-#include <android-base/stringprintf.h>
#include <input/PrintTools.h>
#include <input/VelocityTracker.h>
#include <utils/BitSet.h>
@@ -58,6 +57,9 @@ const bool DEBUG_IMPULSE =
// Nanoseconds per milliseconds.
static const nsecs_t NANOS_PER_MS = 1000000;
+// Seconds per nanosecond.
+static const float SECONDS_PER_NANO = 1E-9;
+
// All axes supported for velocity tracking, mapped to their default strategies.
// Although other strategies are available for testing and comparison purposes,
// the default strategy is the one that applications will actually use. Be very careful
@@ -238,6 +240,11 @@ void VelocityTracker::clearPointer(int32_t pointerId) {
void VelocityTracker::addMovement(nsecs_t eventTime, int32_t pointerId, int32_t axis,
float position) {
+ if (pointerId < 0 || pointerId > MAX_POINTER_ID) {
+ LOG(FATAL) << "Invalid pointer ID " << pointerId << " for axis "
+ << MotionEvent::getLabel(axis);
+ }
+
if (mCurrentPointerIdBits.hasBit(pointerId) &&
std::chrono::nanoseconds(eventTime - mLastEventTime) > ASSUME_POINTER_STOPPED_TIME) {
ALOGD_IF(DEBUG_VELOCITY, "VelocityTracker: stopped for %s, clearing state.",
@@ -261,23 +268,17 @@ void VelocityTracker::addMovement(nsecs_t eventTime, int32_t pointerId, int32_t
mConfiguredStrategies[axis]->addMovement(eventTime, pointerId, position);
if (DEBUG_VELOCITY) {
- ALOGD("VelocityTracker: addMovement eventTime=%" PRId64 ", pointerId=%" PRId32
- ", activePointerId=%s",
- eventTime, pointerId, toString(mActivePointerId).c_str());
-
- std::optional<Estimator> estimator = getEstimator(axis, pointerId);
- ALOGD(" %d: axis=%d, position=%0.3f, "
- "estimator (degree=%d, coeff=%s, confidence=%f)",
- pointerId, axis, position, int((*estimator).degree),
- vectorToString((*estimator).coeff.data(), (*estimator).degree + 1).c_str(),
- (*estimator).confidence);
+ LOG(INFO) << "VelocityTracker: addMovement axis=" << MotionEvent::getLabel(axis)
+ << ", eventTime=" << eventTime << ", pointerId=" << pointerId
+ << ", activePointerId=" << toString(mActivePointerId) << ", position=" << position
+ << ", velocity=" << toString(getVelocity(axis, pointerId));
}
}
-void VelocityTracker::addMovement(const MotionEvent* event) {
+void VelocityTracker::addMovement(const MotionEvent& event) {
// Stores data about which axes to process based on the incoming motion event.
std::set<int32_t> axesToProcess;
- int32_t actionMasked = event->getActionMasked();
+ int32_t actionMasked = event.getActionMasked();
switch (actionMasked) {
case AMOTION_EVENT_ACTION_DOWN:
@@ -290,7 +291,7 @@ void VelocityTracker::addMovement(const MotionEvent* event) {
// Start a new movement trace for a pointer that just went down.
// We do this on down instead of on up because the client may want to query the
// final velocity for a pointer that just went up.
- clearPointer(event->getPointerId(event->getActionIndex()));
+ clearPointer(event.getPointerId(event.getActionIndex()));
axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
break;
}
@@ -299,8 +300,14 @@ void VelocityTracker::addMovement(const MotionEvent* event) {
axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
break;
case AMOTION_EVENT_ACTION_POINTER_UP:
+ if (event.getFlags() & AMOTION_EVENT_FLAG_CANCELED) {
+ clearPointer(event.getPointerId(event.getActionIndex()));
+ return;
+ }
+ // Continue to ACTION_UP to ensure that the POINTER_STOPPED logic is triggered.
+ [[fallthrough]];
case AMOTION_EVENT_ACTION_UP: {
- std::chrono::nanoseconds delaySinceLastEvent(event->getEventTime() - mLastEventTime);
+ std::chrono::nanoseconds delaySinceLastEvent(event.getEventTime() - mLastEventTime);
if (delaySinceLastEvent > ASSUME_POINTER_STOPPED_TIME) {
ALOGD_IF(DEBUG_VELOCITY,
"VelocityTracker: stopped for %s, clearing state upon pointer liftoff.",
@@ -324,21 +331,26 @@ void VelocityTracker::addMovement(const MotionEvent* event) {
case AMOTION_EVENT_ACTION_SCROLL:
axesToProcess.insert(AMOTION_EVENT_AXIS_SCROLL);
break;
+ case AMOTION_EVENT_ACTION_CANCEL: {
+ clear();
+ return;
+ }
+
default:
// Ignore all other actions.
return;
}
- const size_t historySize = event->getHistorySize();
+ const size_t historySize = event.getHistorySize();
for (size_t h = 0; h <= historySize; h++) {
- const nsecs_t eventTime = event->getHistoricalEventTime(h);
- for (size_t i = 0; i < event->getPointerCount(); i++) {
- if (event->isResampled(i, h)) {
+ const nsecs_t eventTime = event.getHistoricalEventTime(h);
+ for (size_t i = 0; i < event.getPointerCount(); i++) {
+ if (event.isResampled(i, h)) {
continue; // skip resampled samples
}
- const int32_t pointerId = event->getPointerId(i);
+ const int32_t pointerId = event.getPointerId(i);
for (int32_t axis : axesToProcess) {
- const float position = event->getHistoricalAxisValue(axis, i, h);
+ const float position = event.getHistoricalAxisValue(axis, i, h);
addMovement(eventTime, pointerId, axis, position);
}
}
@@ -346,9 +358,9 @@ void VelocityTracker::addMovement(const MotionEvent* event) {
}
std::optional<float> VelocityTracker::getVelocity(int32_t axis, int32_t pointerId) const {
- std::optional<Estimator> estimator = getEstimator(axis, pointerId);
- if (estimator && (*estimator).degree >= 1) {
- return (*estimator).coeff[1];
+ const auto& it = mConfiguredStrategies.find(axis);
+ if (it != mConfiguredStrategies.end()) {
+ return it->second->getVelocity(pointerId);
}
return {};
}
@@ -371,57 +383,53 @@ VelocityTracker::ComputedVelocity VelocityTracker::getComputedVelocity(int32_t u
return computedVelocity;
}
-std::optional<VelocityTracker::Estimator> VelocityTracker::getEstimator(int32_t axis,
- int32_t pointerId) const {
- const auto& it = mConfiguredStrategies.find(axis);
- if (it == mConfiguredStrategies.end()) {
- return std::nullopt;
- }
- return it->second->getEstimator(pointerId);
-}
-
-// --- LeastSquaresVelocityTrackerStrategy ---
+AccumulatingVelocityTrackerStrategy::AccumulatingVelocityTrackerStrategy(
+ nsecs_t horizonNanos, bool maintainHorizonDuringAdd)
+ : mHorizonNanos(horizonNanos), mMaintainHorizonDuringAdd(maintainHorizonDuringAdd) {}
-LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(uint32_t degree,
- Weighting weighting)
- : mDegree(degree), mWeighting(weighting) {}
-
-LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {
-}
-
-void LeastSquaresVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
- mIndex.erase(pointerId);
+void AccumulatingVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
mMovements.erase(pointerId);
}
-void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
+void AccumulatingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
float position) {
- // If data for this pointer already exists, we have a valid entry at the position of
- // mIndex[pointerId] and mMovements[pointerId]. In that case, we need to advance the index
- // to the next position in the circular buffer and write the new Movement there. Otherwise,
- // if this is a first movement for this pointer, we initialize the maps mIndex and mMovements
- // for this pointer and write to the first position.
- auto [movementIt, inserted] = mMovements.insert({pointerId, {}});
- auto [indexIt, _] = mIndex.insert({pointerId, 0});
- size_t& index = indexIt->second;
- if (!inserted && movementIt->second[index].eventTime != eventTime) {
+ auto [ringBufferIt, _] = mMovements.try_emplace(pointerId, HISTORY_SIZE);
+ RingBuffer<Movement>& movements = ringBufferIt->second;
+ const size_t size = movements.size();
+
+ if (size != 0 && movements[size - 1].eventTime == eventTime) {
// When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
// of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
// the new pointer. If the eventtimes for both events are identical, just update the data
- // for this time.
+ // for this time (i.e. pop out the last element, and insert the updated movement).
// We only compare against the last value, as it is likely that addMovement is called
// in chronological order as events occur.
- index++;
- }
- if (index == HISTORY_SIZE) {
- index = 0;
+ movements.popBack();
}
- Movement& movement = movementIt->second[index];
- movement.eventTime = eventTime;
- movement.position = position;
+ movements.pushBack({eventTime, position});
+
+ // Clear movements that do not fall within `mHorizonNanos` of the latest movement.
+ // Note that, if in the future we decide to use more movements (i.e. increase HISTORY_SIZE),
+ // we can consider making this step binary-search based, which will give us some improvement.
+ if (mMaintainHorizonDuringAdd) {
+ while (eventTime - movements[0].eventTime > mHorizonNanos) {
+ movements.popFront();
+ }
+ }
}
+// --- LeastSquaresVelocityTrackerStrategy ---
+
+LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(uint32_t degree,
+ Weighting weighting)
+ : AccumulatingVelocityTrackerStrategy(HORIZON /*horizonNanos*/,
+ true /*maintainHorizonDuringAdd*/),
+ mDegree(degree),
+ mWeighting(weighting) {}
+
+LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {}
+
/**
* Solves a linear least squares problem to obtain a N degree polynomial that fits
* the specified input data as nearly as possible.
@@ -471,10 +479,9 @@ void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t
* http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares
* http://en.wikipedia.org/wiki/Gram-Schmidt
*/
-static bool solveLeastSquares(const std::vector<float>& x, const std::vector<float>& y,
- const std::vector<float>& w, uint32_t n,
- std::array<float, VelocityTracker::Estimator::MAX_DEGREE + 1>& outB,
- float* outDet) {
+static std::optional<float> solveLeastSquares(const std::vector<float>& x,
+ const std::vector<float>& y,
+ const std::vector<float>& w, uint32_t n) {
const size_t m = x.size();
ALOGD_IF(DEBUG_STRATEGY, "solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n),
@@ -512,7 +519,7 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo
if (norm < 0.000001f) {
// vectors are linearly dependent or zero so no solution
ALOGD_IF(DEBUG_STRATEGY, " - no solution, norm=%f", norm);
- return false;
+ return {};
}
float invNorm = 1.0f / norm;
@@ -546,6 +553,7 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo
for (uint32_t h = 0; h < m; h++) {
wy[h] = y[h] * w[h];
}
+ std::array<float, VelocityTracker::MAX_DEGREE + 1> outB;
for (uint32_t i = n; i != 0; ) {
i--;
outB[i] = vectorDot(&q[i][0], wy, m);
@@ -567,42 +575,46 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo
}
ymean /= m;
- float sserr = 0;
- float sstot = 0;
- for (uint32_t h = 0; h < m; h++) {
- float err = y[h] - outB[0];
- float term = 1;
- for (uint32_t i = 1; i < n; i++) {
- term *= x[h];
- err -= term * outB[i];
+ if (DEBUG_STRATEGY) {
+ float sserr = 0;
+ float sstot = 0;
+ for (uint32_t h = 0; h < m; h++) {
+ float err = y[h] - outB[0];
+ float term = 1;
+ for (uint32_t i = 1; i < n; i++) {
+ term *= x[h];
+ err -= term * outB[i];
+ }
+ sserr += w[h] * w[h] * err * err;
+ float var = y[h] - ymean;
+ sstot += w[h] * w[h] * var * var;
}
- sserr += w[h] * w[h] * err * err;
- float var = y[h] - ymean;
- sstot += w[h] * w[h] * var * var;
+ ALOGD(" - sserr=%f", sserr);
+ ALOGD(" - sstot=%f", sstot);
}
- *outDet = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1;
- ALOGD_IF(DEBUG_STRATEGY, " - sserr=%f", sserr);
- ALOGD_IF(DEBUG_STRATEGY, " - sstot=%f", sstot);
- ALOGD_IF(DEBUG_STRATEGY, " - det=%f", *outDet);
-
- return true;
+ return outB[1];
}
/*
* Optimized unweighted second-order least squares fit. About 2x speed improvement compared to
* the default implementation
*/
-static std::optional<std::array<float, 3>> solveUnweightedLeastSquaresDeg2(
- const std::vector<float>& x, const std::vector<float>& y) {
- const size_t count = x.size();
- LOG_ALWAYS_FATAL_IF(count != y.size(), "Mismatching array sizes");
- // Solving y = a*x^2 + b*x + c
+std::optional<float> LeastSquaresVelocityTrackerStrategy::solveUnweightedLeastSquaresDeg2(
+ const RingBuffer<Movement>& movements) const {
+ // Solving y = a*x^2 + b*x + c, where
+ // - "x" is age (i.e. duration since latest movement) of the movemnets
+ // - "y" is positions of the movements.
float sxi = 0, sxiyi = 0, syi = 0, sxi2 = 0, sxi3 = 0, sxi2yi = 0, sxi4 = 0;
+ const size_t count = movements.size();
+ const Movement& newestMovement = movements[count - 1];
for (size_t i = 0; i < count; i++) {
- float xi = x[i];
- float yi = y[i];
+ const Movement& movement = movements[i];
+ nsecs_t age = newestMovement.eventTime - movement.eventTime;
+ float xi = -age * SECONDS_PER_NANO;
+ float yi = movement.position;
+
float xi2 = xi*xi;
float xi3 = xi2*xi;
float xi4 = xi3*xi;
@@ -629,124 +641,68 @@ static std::optional<std::array<float, 3>> solveUnweightedLeastSquaresDeg2(
ALOGW("division by 0 when computing velocity, Sxx=%f, Sx2x2=%f, Sxx2=%f", Sxx, Sx2x2, Sxx2);
return std::nullopt;
}
- // Compute a
- float numerator = Sx2y*Sxx - Sxy*Sxx2;
- float a = numerator / denominator;
-
- // Compute b
- numerator = Sxy*Sx2x2 - Sx2y*Sxx2;
- float b = numerator / denominator;
- // Compute c
- float c = syi/count - b * sxi/count - a * sxi2/count;
-
- return std::make_optional(std::array<float, 3>({c, b, a}));
+ return (Sxy * Sx2x2 - Sx2y * Sxx2) / denominator;
}
-std::optional<VelocityTracker::Estimator> LeastSquaresVelocityTrackerStrategy::getEstimator(
- int32_t pointerId) const {
+std::optional<float> LeastSquaresVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
const auto movementIt = mMovements.find(pointerId);
if (movementIt == mMovements.end()) {
return std::nullopt; // no data
}
- // Iterate over movement samples in reverse time order and collect samples.
- std::vector<float> positions;
- std::vector<float> w;
- std::vector<float> time;
- uint32_t index = mIndex.at(pointerId);
- const Movement& newestMovement = movementIt->second[index];
- do {
- const Movement& movement = movementIt->second[index];
-
- nsecs_t age = newestMovement.eventTime - movement.eventTime;
- if (age > HORIZON) {
- break;
- }
- if (movement.eventTime == 0 && index != 0) {
- // All eventTime's are initialized to 0. In this fixed-width circular buffer, it's
- // possible that not all entries are valid. We use a time=0 as a signal for those
- // uninitialized values. If we encounter a time of 0 in a position
- // that's > 0, it means that we hit the block where the data wasn't initialized.
- // We still don't know whether the value at index=0, with eventTime=0 is valid.
- // However, that's only possible when the value is by itself. So there's no hard in
- // processing it anyways, since the velocity for a single point is zero, and this
- // situation will only be encountered in artificial circumstances (in tests).
- // In practice, time will never be 0.
- break;
- }
- positions.push_back(movement.position);
- w.push_back(chooseWeight(pointerId, index));
- time.push_back(-age * 0.000000001f);
- index = (index == 0 ? HISTORY_SIZE : index) - 1;
- } while (positions.size() < HISTORY_SIZE);
-
- const size_t m = positions.size();
- if (m == 0) {
+ const RingBuffer<Movement>& movements = movementIt->second;
+ const size_t size = movements.size();
+ if (size == 0) {
return std::nullopt; // no data
}
- // Calculate a least squares polynomial fit.
uint32_t degree = mDegree;
- if (degree > m - 1) {
- degree = m - 1;
+ if (degree > size - 1) {
+ degree = size - 1;
+ }
+
+ if (degree <= 0) {
+ return std::nullopt;
}
if (degree == 2 && mWeighting == Weighting::NONE) {
// Optimize unweighted, quadratic polynomial fit
- std::optional<std::array<float, 3>> coeff =
- solveUnweightedLeastSquaresDeg2(time, positions);
- if (coeff) {
- VelocityTracker::Estimator estimator;
- estimator.time = newestMovement.eventTime;
- estimator.degree = 2;
- estimator.confidence = 1;
- for (size_t i = 0; i <= estimator.degree; i++) {
- estimator.coeff[i] = (*coeff)[i];
- }
- return estimator;
- }
- } else if (degree >= 1) {
- // General case for an Nth degree polynomial fit
- float det;
- uint32_t n = degree + 1;
- VelocityTracker::Estimator estimator;
- if (solveLeastSquares(time, positions, w, n, estimator.coeff, &det)) {
- estimator.time = newestMovement.eventTime;
- estimator.degree = degree;
- estimator.confidence = det;
-
- ALOGD_IF(DEBUG_STRATEGY, "estimate: degree=%d, coeff=%s, confidence=%f",
- int(estimator.degree), vectorToString(estimator.coeff.data(), n).c_str(),
- estimator.confidence);
-
- return estimator;
- }
+ return solveUnweightedLeastSquaresDeg2(movements);
}
- // No velocity data available for this pointer, but we do have its current position.
- VelocityTracker::Estimator estimator;
- estimator.coeff[0] = positions[0];
- estimator.time = newestMovement.eventTime;
- estimator.degree = 0;
- estimator.confidence = 1;
- return estimator;
+ // Iterate over movement samples in reverse time order and collect samples.
+ std::vector<float> positions;
+ std::vector<float> w;
+ std::vector<float> time;
+
+ const Movement& newestMovement = movements[size - 1];
+ for (ssize_t i = size - 1; i >= 0; i--) {
+ const Movement& movement = movements[i];
+ nsecs_t age = newestMovement.eventTime - movement.eventTime;
+ positions.push_back(movement.position);
+ w.push_back(chooseWeight(pointerId, i));
+ time.push_back(-age * 0.000000001f);
+ }
+
+ // General case for an Nth degree polynomial fit
+ return solveLeastSquares(time, positions, w, degree + 1);
}
float LeastSquaresVelocityTrackerStrategy::chooseWeight(int32_t pointerId, uint32_t index) const {
- const std::array<Movement, HISTORY_SIZE>& movements = mMovements.at(pointerId);
+ const RingBuffer<Movement>& movements = mMovements.at(pointerId);
+ const size_t size = movements.size();
switch (mWeighting) {
case Weighting::DELTA: {
// Weight points based on how much time elapsed between them and the next
// point so that points that "cover" a shorter time span are weighed less.
// delta 0ms: 0.5
// delta 10ms: 1.0
- if (index == mIndex.at(pointerId)) {
+ if (index == size - 1) {
return 1.0f;
}
- uint32_t nextIndex = (index + 1) % HISTORY_SIZE;
float deltaMillis =
- (movements[nextIndex].eventTime - movements[index].eventTime) * 0.000001f;
+ (movements[index + 1].eventTime - movements[index].eventTime) * 0.000001f;
if (deltaMillis < 0) {
return 0.5f;
}
@@ -763,8 +719,7 @@ float LeastSquaresVelocityTrackerStrategy::chooseWeight(int32_t pointerId, uint3
// age 50ms: 1.0
// age 60ms: 0.5
float ageMillis =
- (movements[mIndex.at(pointerId)].eventTime - movements[index].eventTime) *
- 0.000001f;
+ (movements[size - 1].eventTime - movements[index].eventTime) * 0.000001f;
if (ageMillis < 0) {
return 0.5f;
}
@@ -786,8 +741,7 @@ float LeastSquaresVelocityTrackerStrategy::chooseWeight(int32_t pointerId, uint3
// age 50ms: 1.0
// age 100ms: 0.5
float ageMillis =
- (movements[mIndex.at(pointerId)].eventTime - movements[index].eventTime) *
- 0.000001f;
+ (movements[size - 1].eventTime - movements[index].eventTime) * 0.000001f;
if (ageMillis < 50) {
return 1.0f;
}
@@ -827,13 +781,9 @@ void IntegratingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t
mPointerIdBits.markBit(pointerId);
}
-std::optional<VelocityTracker::Estimator> IntegratingVelocityTrackerStrategy::getEstimator(
- int32_t pointerId) const {
+std::optional<float> IntegratingVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
if (mPointerIdBits.hasBit(pointerId)) {
- const State& state = mPointerState[pointerId];
- VelocityTracker::Estimator estimator;
- populateEstimator(state, &estimator);
- return estimator;
+ return mPointerState[pointerId].vel;
}
return std::nullopt;
@@ -883,77 +833,39 @@ void IntegratingVelocityTrackerStrategy::updateState(State& state, nsecs_t event
state.pos = pos;
}
-void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state,
- VelocityTracker::Estimator* outEstimator) const {
- outEstimator->time = state.updateTime;
- outEstimator->confidence = 1.0f;
- outEstimator->degree = state.degree;
- outEstimator->coeff[0] = state.pos;
- outEstimator->coeff[1] = state.vel;
- outEstimator->coeff[2] = state.accel / 2;
-}
-
-
// --- LegacyVelocityTrackerStrategy ---
-LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() {}
+LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy()
+ : AccumulatingVelocityTrackerStrategy(HORIZON /*horizonNanos*/,
+ false /*maintainHorizonDuringAdd*/) {}
LegacyVelocityTrackerStrategy::~LegacyVelocityTrackerStrategy() {
}
-void LegacyVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
- mIndex.erase(pointerId);
- mMovements.erase(pointerId);
-}
-
-void LegacyVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
- float position) {
- // If data for this pointer already exists, we have a valid entry at the position of
- // mIndex[pointerId] and mMovements[pointerId]. In that case, we need to advance the index
- // to the next position in the circular buffer and write the new Movement there. Otherwise,
- // if this is a first movement for this pointer, we initialize the maps mIndex and mMovements
- // for this pointer and write to the first position.
- auto [movementIt, inserted] = mMovements.insert({pointerId, {}});
- auto [indexIt, _] = mIndex.insert({pointerId, 0});
- size_t& index = indexIt->second;
- if (!inserted && movementIt->second[index].eventTime != eventTime) {
- // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
- // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
- // the new pointer. If the eventtimes for both events are identical, just update the data
- // for this time.
- // We only compare against the last value, as it is likely that addMovement is called
- // in chronological order as events occur.
- index++;
- }
- if (index == HISTORY_SIZE) {
- index = 0;
- }
-
- Movement& movement = movementIt->second[index];
- movement.eventTime = eventTime;
- movement.position = position;
-}
-
-std::optional<VelocityTracker::Estimator> LegacyVelocityTrackerStrategy::getEstimator(
- int32_t pointerId) const {
+std::optional<float> LegacyVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
const auto movementIt = mMovements.find(pointerId);
if (movementIt == mMovements.end()) {
return std::nullopt; // no data
}
- const Movement& newestMovement = movementIt->second[mIndex.at(pointerId)];
+
+ const RingBuffer<Movement>& movements = movementIt->second;
+ const size_t size = movements.size();
+ if (size == 0) {
+ return std::nullopt; // no data
+ }
+
+ const Movement& newestMovement = movements[size - 1];
// Find the oldest sample that contains the pointer and that is not older than HORIZON.
nsecs_t minTime = newestMovement.eventTime - HORIZON;
- uint32_t oldestIndex = mIndex.at(pointerId);
- uint32_t numTouches = 1;
- do {
- uint32_t nextOldestIndex = (oldestIndex == 0 ? HISTORY_SIZE : oldestIndex) - 1;
- const Movement& nextOldestMovement = mMovements.at(pointerId)[nextOldestIndex];
+ uint32_t oldestIndex = size - 1;
+ for (ssize_t i = size - 1; i >= 0; i--) {
+ const Movement& nextOldestMovement = movements[i];
if (nextOldestMovement.eventTime < minTime) {
break;
}
- oldestIndex = nextOldestIndex;
- } while (++numTouches < HISTORY_SIZE);
+ oldestIndex = i;
+ }
// Calculate an exponentially weighted moving average of the velocity estimate
// at different points in time measured relative to the oldest sample.
@@ -967,17 +879,13 @@ std::optional<VelocityTracker::Estimator> LegacyVelocityTrackerStrategy::getEsti
// 16ms apart but some consecutive samples could be only 0.5sm apart because
// the hardware or driver reports them irregularly or in bursts.
float accumV = 0;
- uint32_t index = oldestIndex;
uint32_t samplesUsed = 0;
- const Movement& oldestMovement = mMovements.at(pointerId)[oldestIndex];
+ const Movement& oldestMovement = movements[oldestIndex];
float oldestPosition = oldestMovement.position;
nsecs_t lastDuration = 0;
- while (numTouches-- > 1) {
- if (++index == HISTORY_SIZE) {
- index = 0;
- }
- const Movement& movement = mMovements.at(pointerId)[index];
+ for (size_t i = oldestIndex; i < size; i++) {
+ const Movement& movement = movements[i];
nsecs_t duration = movement.eventTime - oldestMovement.eventTime;
// If the duration between samples is small, we may significantly overestimate
@@ -993,62 +901,22 @@ std::optional<VelocityTracker::Estimator> LegacyVelocityTrackerStrategy::getEsti
}
}
- // Report velocity.
- float newestPosition = newestMovement.position;
- VelocityTracker::Estimator estimator;
- estimator.time = newestMovement.eventTime;
- estimator.confidence = 1;
- estimator.coeff[0] = newestPosition;
if (samplesUsed) {
- estimator.coeff[1] = accumV;
- estimator.degree = 1;
- } else {
- estimator.degree = 0;
+ return accumV;
}
- return estimator;
+ return std::nullopt;
}
// --- ImpulseVelocityTrackerStrategy ---
ImpulseVelocityTrackerStrategy::ImpulseVelocityTrackerStrategy(bool deltaValues)
- : mDeltaValues(deltaValues) {}
+ : AccumulatingVelocityTrackerStrategy(HORIZON /*horizonNanos*/,
+ true /*maintainHorizonDuringAdd*/),
+ mDeltaValues(deltaValues) {}
ImpulseVelocityTrackerStrategy::~ImpulseVelocityTrackerStrategy() {
}
-void ImpulseVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
- mIndex.erase(pointerId);
- mMovements.erase(pointerId);
-}
-
-void ImpulseVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
- float position) {
- // If data for this pointer already exists, we have a valid entry at the position of
- // mIndex[pointerId] and mMovements[pointerId]. In that case, we need to advance the index
- // to the next position in the circular buffer and write the new Movement there. Otherwise,
- // if this is a first movement for this pointer, we initialize the maps mIndex and mMovements
- // for this pointer and write to the first position.
- auto [movementIt, inserted] = mMovements.insert({pointerId, {}});
- auto [indexIt, _] = mIndex.insert({pointerId, 0});
- size_t& index = indexIt->second;
- if (!inserted && movementIt->second[index].eventTime != eventTime) {
- // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
- // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
- // the new pointer. If the eventtimes for both events are identical, just update the data
- // for this time.
- // We only compare against the last value, as it is likely that addMovement is called
- // in chronological order as events occur.
- index++;
- }
- if (index == HISTORY_SIZE) {
- index = 0;
- }
-
- Movement& movement = movementIt->second[index];
- movement.eventTime = eventTime;
- movement.position = position;
-}
-
/**
* Calculate the total impulse provided to the screen and the resulting velocity.
*
@@ -1123,112 +991,44 @@ static float kineticEnergyToVelocity(float work) {
return (work < 0 ? -1.0 : 1.0) * sqrtf(fabsf(work)) * sqrt2;
}
-static float calculateImpulseVelocity(const nsecs_t* t, const float* x, size_t count,
- bool deltaValues) {
- // The input should be in reversed time order (most recent sample at index i=0)
- // t[i] is in nanoseconds, but due to FP arithmetic, convert to seconds inside this function
- static constexpr float SECONDS_PER_NANO = 1E-9;
-
- if (count < 2) {
- return 0; // if 0 or 1 points, velocity is zero
- }
- if (t[1] > t[0]) { // Algorithm will still work, but not perfectly
- ALOGE("Samples provided to calculateImpulseVelocity in the wrong order");
- }
-
- // If the data values are delta values, we do not have to calculate deltas here.
- // We can use the delta values directly, along with the calculated time deltas.
- // Since the data value input is in reversed time order:
- // [a] for non-delta inputs, instantenous velocity = (x[i] - x[i-1])/(t[i] - t[i-1])
- // [b] for delta inputs, instantenous velocity = -x[i-1]/(t[i] - t[i - 1])
- // e.g., let the non-delta values are: V = [2, 3, 7], the equivalent deltas are D = [2, 1, 4].
- // Since the input is in reversed time order, the input values for this function would be
- // V'=[7, 3, 2] and D'=[4, 1, 2] for the non-delta and delta values, respectively.
- //
- // The equivalent of {(V'[2] - V'[1]) = 2 - 3 = -1} would be {-D'[1] = -1}
- // Similarly, the equivalent of {(V'[1] - V'[0]) = 3 - 7 = -4} would be {-D'[0] = -4}
-
- if (count == 2) { // if 2 points, basic linear calculation
- if (t[1] == t[0]) {
- ALOGE("Events have identical time stamps t=%" PRId64 ", setting velocity = 0", t[0]);
- return 0;
- }
- const float deltaX = deltaValues ? -x[0] : x[1] - x[0];
- return deltaX / (SECONDS_PER_NANO * (t[1] - t[0]));
- }
- // Guaranteed to have at least 3 points here
- float work = 0;
- for (size_t i = count - 1; i > 0 ; i--) { // start with the oldest sample and go forward in time
- if (t[i] == t[i-1]) {
- ALOGE("Events have identical time stamps t=%" PRId64 ", skipping sample", t[i]);
- continue;
- }
- float vprev = kineticEnergyToVelocity(work); // v[i-1]
- const float deltaX = deltaValues ? -x[i-1] : x[i] - x[i-1];
- float vcurr = deltaX / (SECONDS_PER_NANO * (t[i] - t[i-1])); // v[i]
- work += (vcurr - vprev) * fabsf(vcurr);
- if (i == count - 1) {
- work *= 0.5; // initial condition, case 2) above
- }
- }
- return kineticEnergyToVelocity(work);
-}
-
-std::optional<VelocityTracker::Estimator> ImpulseVelocityTrackerStrategy::getEstimator(
- int32_t pointerId) const {
+std::optional<float> ImpulseVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
const auto movementIt = mMovements.find(pointerId);
if (movementIt == mMovements.end()) {
return std::nullopt; // no data
}
- // Iterate over movement samples in reverse time order and collect samples.
- float positions[HISTORY_SIZE];
- nsecs_t time[HISTORY_SIZE];
- size_t m = 0; // number of points that will be used for fitting
- size_t index = mIndex.at(pointerId);
- const Movement& newestMovement = movementIt->second[index];
- do {
- const Movement& movement = movementIt->second[index];
+ const RingBuffer<Movement>& movements = movementIt->second;
+ const size_t size = movements.size();
+ if (size == 0) {
+ return std::nullopt; // no data
+ }
- nsecs_t age = newestMovement.eventTime - movement.eventTime;
- if (age > HORIZON) {
- break;
- }
- if (movement.eventTime == 0 && index != 0) {
- // All eventTime's are initialized to 0. If we encounter a time of 0 in a position
- // that's >0, it means that we hit the block where the data wasn't initialized.
- // It's also possible that the sample at 0 would be invalid, but there's no harm in
- // processing it, since it would be just a single point, and will only be encountered
- // in artificial circumstances (in tests).
- break;
- }
+ float work = 0;
+ for (size_t i = 0; i < size - 1; i++) {
+ const Movement& mvt = movements[i];
+ const Movement& nextMvt = movements[i + 1];
- positions[m] = movement.position;
- time[m] = movement.eventTime;
- index = (index == 0 ? HISTORY_SIZE : index) - 1;
- } while (++m < HISTORY_SIZE);
+ float vprev = kineticEnergyToVelocity(work);
+ float delta = mDeltaValues ? nextMvt.position : nextMvt.position - mvt.position;
+ float vcurr = delta / (SECONDS_PER_NANO * (nextMvt.eventTime - mvt.eventTime));
+ work += (vcurr - vprev) * fabsf(vcurr);
- if (m == 0) {
- return std::nullopt; // no data
+ if (i == 0) {
+ work *= 0.5; // initial condition, case 2) above
+ }
}
- VelocityTracker::Estimator estimator;
- estimator.coeff[0] = 0;
- estimator.coeff[1] = calculateImpulseVelocity(time, positions, m, mDeltaValues);
- estimator.coeff[2] = 0;
-
- estimator.time = newestMovement.eventTime;
- estimator.degree = 2; // similar results to 2nd degree fit
- estimator.confidence = 1;
- ALOGD_IF(DEBUG_STRATEGY, "velocity: %.1f", estimator.coeff[1]);
+ const float velocity = kineticEnergyToVelocity(work);
+ ALOGD_IF(DEBUG_STRATEGY, "velocity: %.1f", velocity);
if (DEBUG_IMPULSE) {
// TODO(b/134179997): delete this block once the switch to 'impulse' is complete.
// Calculate the lsq2 velocity for the same inputs to allow runtime comparisons.
// X axis chosen arbitrarily for velocity comparisons.
VelocityTracker lsq2(VelocityTracker::Strategy::LSQ2);
- for (ssize_t i = m - 1; i >= 0; i--) {
- lsq2.addMovement(time[i], pointerId, AMOTION_EVENT_AXIS_X, positions[i]);
+ for (size_t i = 0; i < size; i++) {
+ const Movement& mvt = movements[i];
+ lsq2.addMovement(mvt.eventTime, pointerId, AMOTION_EVENT_AXIS_X, mvt.position);
}
std::optional<float> v = lsq2.getVelocity(AMOTION_EVENT_AXIS_X, pointerId);
if (v) {
@@ -1237,7 +1037,7 @@ std::optional<VelocityTracker::Estimator> ImpulseVelocityTrackerStrategy::getEst
ALOGD("lsq2 velocity: could not compute velocity");
}
}
- return estimator;
+ return velocity;
}
} // namespace android
diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp
index 9a459b135c..db7031ab03 100644
--- a/libs/input/VirtualInputDevice.cpp
+++ b/libs/input/VirtualInputDevice.cpp
@@ -193,6 +193,7 @@ const std::map<int, int> VirtualKeyboard::KEY_CODE_MAPPING = {
{AKEYCODE_NUMPAD_ENTER, KEY_KPENTER},
{AKEYCODE_NUMPAD_EQUALS, KEY_KPEQUAL},
{AKEYCODE_NUMPAD_COMMA, KEY_KPCOMMA},
+ {AKEYCODE_LANGUAGE_SWITCH, KEY_LANGUAGE},
};
VirtualKeyboard::VirtualKeyboard(unique_fd fd) : VirtualInputDevice(std::move(fd)) {}
VirtualKeyboard::~VirtualKeyboard() {}
diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl
index dab843b48f..8f6f95b7d1 100644
--- a/libs/input/android/os/IInputConstants.aidl
+++ b/libs/input/android/os/IInputConstants.aidl
@@ -57,4 +57,88 @@ interface IInputConstants
/* The default pointer acceleration value. */
const int DEFAULT_POINTER_ACCELERATION = 3;
+
+ /**
+ * Use the default Velocity Tracker Strategy. Different axes may use different default
+ * strategies.
+ */
+ const int VELOCITY_TRACKER_STRATEGY_DEFAULT = -1;
+
+ /**
+ * Velocity Tracker Strategy: Impulse.
+ * Physical model of pushing an object. Quality: VERY GOOD.
+ * Works with duplicate coordinates, unclean finger liftoff.
+ */
+ const int VELOCITY_TRACKER_STRATEGY_IMPULSE = 0;
+
+ /**
+ * Velocity Tracker Strategy: LSQ1.
+ * 1st order least squares. Quality: POOR.
+ * Frequently underfits the touch data especially when the finger accelerates
+ * or changes direction. Often underestimates velocity. The direction
+ * is overly influenced by historical touch points.
+ */
+ const int VELOCITY_TRACKER_STRATEGY_LSQ1 = 1;
+
+ /**
+ * Velocity Tracker Strategy: LSQ2.
+ * 2nd order least squares. Quality: VERY GOOD.
+ * Pretty much ideal, but can be confused by certain kinds of touch data,
+ * particularly if the panel has a tendency to generate delayed,
+ * duplicate or jittery touch coordinates when the finger is released.
+ */
+ const int VELOCITY_TRACKER_STRATEGY_LSQ2 = 2;
+
+ /**
+ * Velocity Tracker Strategy: LSQ3.
+ * 3rd order least squares. Quality: UNUSABLE.
+ * Frequently overfits the touch data yielding wildly divergent estimates
+ * of the velocity when the finger is released.
+ */
+ const int VELOCITY_TRACKER_STRATEGY_LSQ3 = 3;
+
+ /**
+ * Velocity Tracker Strategy: WLSQ2_DELTA.
+ * 2nd order weighted least squares, delta weighting. Quality: EXPERIMENTAL
+ */
+ const int VELOCITY_TRACKER_STRATEGY_WLSQ2_DELTA = 4;
+
+ /**
+ * Velocity Tracker Strategy: WLSQ2_CENTRAL.
+ * 2nd order weighted least squares, central weighting. Quality: EXPERIMENTALe
+ */
+ const int VELOCITY_TRACKER_STRATEGY_WLSQ2_CENTRAL = 5;
+
+ /**
+ * Velocity Tracker Strategy: WLSQ2_RECENT.
+ * 2nd order weighted least squares, recent weighting. Quality: EXPERIMENTAL
+ */
+ const int VELOCITY_TRACKER_STRATEGY_WLSQ2_RECENT = 6;
+
+ /**
+ * Velocity Tracker Strategy: INT1.
+ * 1st order integrating filter. Quality: GOOD.
+ * Not as good as 'lsq2' because it cannot estimate acceleration but it is
+ * more tolerant of errors. Like 'lsq1', this strategy tends to underestimate
+ * the velocity of a fling but this strategy tends to respond to changes in
+ * direction more quickly and accurately.
+ */
+ const int VELOCITY_TRACKER_STRATEGY_INT1 = 7;
+
+ /**
+ * Velocity Tracker Strategy: INT2.
+ * 2nd order integrating filter. Quality: EXPERIMENTAL.
+ * For comparison purposes only. Unlike 'int1' this strategy can compensate
+ * for acceleration but it typically overestimates the effect.
+ */
+ const int VELOCITY_TRACKER_STRATEGY_INT2 = 8;
+
+ /**
+ * Velocity Tracker Strategy: Legacy.
+ * Legacy velocity tracker algorithm. Quality: POOR.
+ * For comparison purposes only. This algorithm is strongly influenced by
+ * old data points, consistently underestimates velocity and takes a very long
+ * time to adjust to changes in direction.
+ */
+ const int VELOCITY_TRACKER_STRATEGY_LEGACY = 9;
}
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
new file mode 100644
index 0000000000..54eeb39935
--- /dev/null
+++ b/libs/input/input_flags.aconfig
@@ -0,0 +1,92 @@
+package: "com.android.input.flags"
+
+flag {
+ name: "enable_outbound_event_verification"
+ namespace: "input"
+ description: "Set to true to enable crashing whenever bad outbound events are detected inside InputTransport"
+ bug: "271455682"
+}
+
+flag {
+ name: "enable_inbound_event_verification"
+ namespace: "input"
+ description: "Set to true to enable crashing whenever bad inbound events are going into InputDispatcher"
+ bug: "271455682"
+}
+
+flag {
+ name: "enable_pointer_choreographer"
+ namespace: "input"
+ description: "Set to true to enable PointerChoreographer: the new pipeline for showing pointer icons"
+ bug: "293587049"
+}
+
+flag {
+ name: "enable_gestures_library_timer_provider"
+ namespace: "input"
+ description: "Set to true to enable timer support for the touchpad Gestures library"
+ bug: "297192727"
+}
+
+flag {
+ name: "enable_multi_device_input"
+ namespace: "input"
+ description: "Set to true to enable multi-device input: touch and stylus can be active at the same time, but in different windows"
+ bug: "211379801"
+}
+
+flag {
+ name: "a11y_crash_on_inconsistent_event_stream"
+ namespace: "accessibility"
+ description: "Brings back fatal logging for inconsistent event streams originating from accessibility."
+ bug: "299977100"
+}
+
+flag {
+ name: "report_palms_to_gestures_library"
+ namespace: "input"
+ description: "Report touches marked as palm by firmware to gestures library"
+ bug: "302505955"
+}
+
+flag {
+ name: "enable_touchpad_typing_palm_rejection"
+ namespace: "input"
+ description: "Enable additional palm rejection on touchpad while typing"
+ bug: "301055381"
+}
+
+flag {
+ name: "remove_app_switch_drops"
+ namespace: "input"
+ description: "Remove the logic of dropping events due to pending app switch"
+ bug: "284808102"
+}
+
+flag {
+ name: "disable_reject_touch_on_stylus_hover"
+ namespace: "input"
+ description: "Disable touch rejection when the stylus hovers the screen"
+ bug: "301216095"
+}
+
+flag {
+ name: "enable_input_filter_rust_impl"
+ namespace: "input"
+ description: "Enable input filter rust implementation"
+ bug: "294546335"
+}
+
+flag {
+ name: "override_key_behavior_permission_apis"
+ namespace: "input"
+ description: "enable override key behavior permission APIs"
+ bug: "309018874"
+}
+
+flag {
+ name: "remove_pointer_event_tracking_in_wm"
+ namespace: "input"
+ description: "Remove pointer event tracking in WM after the Pointer Icon Refactor"
+ bug: "315321016"
+}
diff --git a/libs/input/input_verifier.rs b/libs/input/input_verifier.rs
deleted file mode 100644
index f8dda3c901..0000000000
--- a/libs/input/input_verifier.rs
+++ /dev/null
@@ -1,419 +0,0 @@
-/*
- * Copyright 2023 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.
- */
-
-//! Validate the incoming motion stream.
-//! This class is not thread-safe.
-//! State is stored in the "InputVerifier" object
-//! that can be created via the 'create' method.
-//! Usage:
-//! Box<InputVerifier> verifier = create("inputChannel name");
-//! result = process_movement(verifier, ...);
-//! if (result) {
-//! crash(result.error_message());
-//! }
-
-use std::collections::HashMap;
-use std::collections::HashSet;
-
-use bitflags::bitflags;
-use log::info;
-
-#[cxx::bridge(namespace = "android::input")]
-#[allow(unsafe_op_in_unsafe_fn)]
-mod ffi {
- #[namespace = "android"]
- unsafe extern "C++" {
- include!("ffi/FromRustToCpp.h");
- fn shouldLog(tag: &str) -> bool;
- }
- #[namespace = "android::input::verifier"]
- extern "Rust" {
- type InputVerifier;
-
- fn create(name: String) -> Box<InputVerifier>;
- fn process_movement(
- verifier: &mut InputVerifier,
- device_id: i32,
- action: u32,
- pointer_properties: &[RustPointerProperties],
- flags: i32,
- ) -> String;
- }
-
- pub struct RustPointerProperties {
- id: i32,
- }
-}
-
-use crate::ffi::shouldLog;
-use crate::ffi::RustPointerProperties;
-
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
-struct DeviceId(i32);
-
-fn process_movement(
- verifier: &mut InputVerifier,
- device_id: i32,
- action: u32,
- pointer_properties: &[RustPointerProperties],
- flags: i32,
-) -> String {
- let result = verifier.process_movement(
- DeviceId(device_id),
- action,
- pointer_properties,
- Flags::from_bits(flags).unwrap(),
- );
- match result {
- Ok(()) => "".to_string(),
- Err(e) => e,
- }
-}
-
-fn create(name: String) -> Box<InputVerifier> {
- Box::new(InputVerifier::new(&name))
-}
-
-#[repr(u32)]
-enum MotionAction {
- Down = input_bindgen::AMOTION_EVENT_ACTION_DOWN,
- Up = input_bindgen::AMOTION_EVENT_ACTION_UP,
- Move = input_bindgen::AMOTION_EVENT_ACTION_MOVE,
- Cancel = input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
- Outside = input_bindgen::AMOTION_EVENT_ACTION_OUTSIDE,
- PointerDown { action_index: usize } = input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN,
- PointerUp { action_index: usize } = input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP,
- HoverEnter = input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
- HoverMove = input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
- HoverExit = input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT,
- Scroll = input_bindgen::AMOTION_EVENT_ACTION_SCROLL,
- ButtonPress = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
- ButtonRelease = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
-}
-
-fn get_action_index(action: u32) -> usize {
- let index = (action & input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
- >> input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
- index.try_into().unwrap()
-}
-
-impl From<u32> for MotionAction {
- fn from(action: u32) -> Self {
- let action_masked = action & input_bindgen::AMOTION_EVENT_ACTION_MASK;
- let action_index = get_action_index(action);
- match action_masked {
- input_bindgen::AMOTION_EVENT_ACTION_DOWN => MotionAction::Down,
- input_bindgen::AMOTION_EVENT_ACTION_UP => MotionAction::Up,
- input_bindgen::AMOTION_EVENT_ACTION_MOVE => MotionAction::Move,
- input_bindgen::AMOTION_EVENT_ACTION_CANCEL => MotionAction::Cancel,
- input_bindgen::AMOTION_EVENT_ACTION_OUTSIDE => MotionAction::Outside,
- input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN => {
- MotionAction::PointerDown { action_index }
- }
- input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP => {
- MotionAction::PointerUp { action_index }
- }
- input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER => MotionAction::HoverEnter,
- input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE => MotionAction::HoverMove,
- input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT => MotionAction::HoverExit,
- input_bindgen::AMOTION_EVENT_ACTION_SCROLL => MotionAction::Scroll,
- input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS => MotionAction::ButtonPress,
- input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE => MotionAction::ButtonRelease,
- _ => panic!("Unknown action: {}", action),
- }
- }
-}
-
-bitflags! {
- struct Flags: i32 {
- const CANCELED = input_bindgen::AMOTION_EVENT_FLAG_CANCELED;
- }
-}
-
-fn motion_action_to_string(action: u32) -> String {
- match action.into() {
- MotionAction::Down => "DOWN".to_string(),
- MotionAction::Up => "UP".to_string(),
- MotionAction::Move => "MOVE".to_string(),
- MotionAction::Cancel => "CANCEL".to_string(),
- MotionAction::Outside => "OUTSIDE".to_string(),
- MotionAction::PointerDown { action_index } => {
- format!("POINTER_DOWN({})", action_index)
- }
- MotionAction::PointerUp { action_index } => {
- format!("POINTER_UP({})", action_index)
- }
- MotionAction::HoverMove => "HOVER_MOVE".to_string(),
- MotionAction::Scroll => "SCROLL".to_string(),
- MotionAction::HoverEnter => "HOVER_ENTER".to_string(),
- MotionAction::HoverExit => "HOVER_EXIT".to_string(),
- MotionAction::ButtonPress => "BUTTON_PRESS".to_string(),
- MotionAction::ButtonRelease => "BUTTON_RELEASE".to_string(),
- }
-}
-
-/**
- * Log all of the movements that are sent to this verifier. Helps to identify the streams that lead
- * to inconsistent events.
- * Enable this via "adb shell setprop log.tag.InputVerifierLogEvents DEBUG"
- */
-fn log_events() -> bool {
- shouldLog("InputVerifierLogEvents")
-}
-
-struct InputVerifier {
- name: String,
- touching_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
-}
-
-impl InputVerifier {
- fn new(name: &str) -> Self {
- logger::init(
- logger::Config::default()
- .with_tag_on_device("InputVerifier")
- .with_min_level(log::Level::Trace),
- );
- Self { name: name.to_owned(), touching_pointer_ids_by_device: HashMap::new() }
- }
-
- fn process_movement(
- &mut self,
- device_id: DeviceId,
- action: u32,
- pointer_properties: &[RustPointerProperties],
- flags: Flags,
- ) -> Result<(), String> {
- if log_events() {
- info!(
- "Processing {} for device {:?} ({} pointer{}) on {}",
- motion_action_to_string(action),
- device_id,
- pointer_properties.len(),
- if pointer_properties.len() == 1 { "" } else { "s" },
- self.name
- );
- }
-
- match action.into() {
- MotionAction::Down => {
- let it = self.touching_pointer_ids_by_device.entry(device_id).or_default();
- let pointer_id = pointer_properties[0].id;
- if it.contains(&pointer_id) {
- return Err(format!(
- "{}: Invalid DOWN event - pointers already down for device {:?}: {:?}",
- self.name, device_id, it
- ));
- }
- it.insert(pointer_id);
- }
- MotionAction::PointerDown { action_index } => {
- if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
- return Err(format!(
- "{}: Received POINTER_DOWN but no pointers are currently down \
- for device {:?}",
- self.name, device_id
- ));
- }
- let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
- let pointer_id = pointer_properties[action_index].id;
- if it.contains(&pointer_id) {
- return Err(format!(
- "{}: Pointer with id={} not found in the properties",
- self.name, pointer_id
- ));
- }
- it.insert(pointer_id);
- }
- MotionAction::Move => {
- if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
- return Err(format!(
- "{}: ACTION_MOVE touching pointers don't match",
- self.name
- ));
- }
- }
- MotionAction::PointerUp { action_index } => {
- if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
- return Err(format!(
- "{}: Received POINTER_UP but no pointers are currently down for device \
- {:?}",
- self.name, device_id
- ));
- }
- let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
- let pointer_id = pointer_properties[action_index].id;
- it.remove(&pointer_id);
- }
- MotionAction::Up => {
- if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
- return Err(format!(
- "{} Received ACTION_UP but no pointers are currently down for device {:?}",
- self.name, device_id
- ));
- }
- let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
- if it.len() != 1 {
- return Err(format!(
- "{}: Got ACTION_UP, but we have pointers: {:?} for device {:?}",
- self.name, it, device_id
- ));
- }
- let pointer_id = pointer_properties[0].id;
- if !it.contains(&pointer_id) {
- return Err(format!(
- "{}: Got ACTION_UP, but pointerId {} is not touching. Touching pointers:\
- {:?} for device {:?}",
- self.name, pointer_id, it, device_id
- ));
- }
- it.clear();
- }
- MotionAction::Cancel => {
- if flags.contains(Flags::CANCELED) {
- return Err(format!(
- "{}: For ACTION_CANCEL, must set FLAG_CANCELED",
- self.name
- ));
- }
- if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
- return Err(format!(
- "{}: Got ACTION_CANCEL, but the pointers don't match. \
- Existing pointers: {:?}",
- self.name, self.touching_pointer_ids_by_device
- ));
- }
- self.touching_pointer_ids_by_device.remove(&device_id);
- }
- _ => return Ok(()),
- }
- Ok(())
- }
-
- fn ensure_touching_pointers_match(
- &self,
- device_id: DeviceId,
- pointer_properties: &[RustPointerProperties],
- ) -> bool {
- let Some(pointers) = self.touching_pointer_ids_by_device.get(&device_id) else {
- return false;
- };
-
- for pointer_property in pointer_properties.iter() {
- let pointer_id = pointer_property.id;
- if !pointers.contains(&pointer_id) {
- return false;
- }
- }
- true
- }
-}
-
-#[cfg(test)]
-mod tests {
- use crate::DeviceId;
- use crate::Flags;
- use crate::InputVerifier;
- use crate::RustPointerProperties;
- #[test]
- fn single_pointer_stream() {
- let mut verifier = InputVerifier::new("Test");
- let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
- assert!(verifier
- .process_movement(
- DeviceId(1),
- input_bindgen::AMOTION_EVENT_ACTION_DOWN,
- &pointer_properties,
- Flags::empty(),
- )
- .is_ok());
- assert!(verifier
- .process_movement(
- DeviceId(1),
- input_bindgen::AMOTION_EVENT_ACTION_MOVE,
- &pointer_properties,
- Flags::empty(),
- )
- .is_ok());
- assert!(verifier
- .process_movement(
- DeviceId(1),
- input_bindgen::AMOTION_EVENT_ACTION_UP,
- &pointer_properties,
- Flags::empty(),
- )
- .is_ok());
- }
-
- #[test]
- fn multi_device_stream() {
- let mut verifier = InputVerifier::new("Test");
- let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
- assert!(verifier
- .process_movement(
- DeviceId(1),
- input_bindgen::AMOTION_EVENT_ACTION_DOWN,
- &pointer_properties,
- Flags::empty(),
- )
- .is_ok());
- assert!(verifier
- .process_movement(
- DeviceId(1),
- input_bindgen::AMOTION_EVENT_ACTION_MOVE,
- &pointer_properties,
- Flags::empty(),
- )
- .is_ok());
- assert!(verifier
- .process_movement(
- DeviceId(2),
- input_bindgen::AMOTION_EVENT_ACTION_DOWN,
- &pointer_properties,
- Flags::empty(),
- )
- .is_ok());
- assert!(verifier
- .process_movement(
- DeviceId(2),
- input_bindgen::AMOTION_EVENT_ACTION_MOVE,
- &pointer_properties,
- Flags::empty(),
- )
- .is_ok());
- assert!(verifier
- .process_movement(
- DeviceId(1),
- input_bindgen::AMOTION_EVENT_ACTION_UP,
- &pointer_properties,
- Flags::empty(),
- )
- .is_ok());
- }
-
- #[test]
- fn test_invalid_up() {
- let mut verifier = InputVerifier::new("Test");
- let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
- assert!(verifier
- .process_movement(
- DeviceId(1),
- input_bindgen::AMOTION_EVENT_ACTION_UP,
- &pointer_properties,
- Flags::empty(),
- )
- .is_err());
- }
-}
diff --git a/libs/input/rust/Android.bp b/libs/input/rust/Android.bp
new file mode 100644
index 0000000000..018d199ce2
--- /dev/null
+++ b/libs/input/rust/Android.bp
@@ -0,0 +1,72 @@
+// Copyright 2023 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.
+
+rust_defaults {
+ name: "libinput_rust_defaults",
+ crate_name: "input",
+ srcs: ["lib.rs"],
+ host_supported: true,
+ rustlibs: [
+ "libbitflags",
+ "libcxx",
+ "libinput_bindgen",
+ "liblogger",
+ "liblog_rust",
+ "inputconstants-rust",
+ ],
+ whole_static_libs: [
+ "libinput_from_rust_to_cpp",
+ ],
+ shared_libs: [
+ "libbase",
+ ],
+}
+
+rust_library {
+ name: "libinput_rust",
+ defaults: ["libinput_rust_defaults"],
+}
+
+rust_ffi_static {
+ name: "libinput_rust_ffi",
+ defaults: ["libinput_rust_defaults"],
+}
+
+rust_test {
+ name: "libinput_rust_test",
+ defaults: ["libinput_rust_defaults"],
+ test_options: {
+ unit_test: true,
+ },
+ test_suites: ["device_tests"],
+ sanitize: {
+ hwaddress: true,
+ },
+}
+
+genrule {
+ name: "libinput_cxx_bridge_code",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) >> $(out)",
+ srcs: ["lib.rs"],
+ out: ["input_cxx_bridge_generated.cpp"],
+}
+
+genrule {
+ name: "libinput_cxx_bridge_header",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) --header >> $(out)",
+ srcs: ["lib.rs"],
+ out: ["input_cxx_bridge.rs.h"],
+}
diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs
new file mode 100644
index 0000000000..705c959d04
--- /dev/null
+++ b/libs/input/rust/input.rs
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2023 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.
+ */
+
+//! Common definitions of the Android Input Framework in rust.
+
+use bitflags::bitflags;
+use std::fmt;
+
+/// The InputDevice ID.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub struct DeviceId(pub i32);
+
+#[repr(u32)]
+pub enum SourceClass {
+ None = input_bindgen::AINPUT_SOURCE_CLASS_NONE,
+ Button = input_bindgen::AINPUT_SOURCE_CLASS_BUTTON,
+ Pointer = input_bindgen::AINPUT_SOURCE_CLASS_POINTER,
+ Navigation = input_bindgen::AINPUT_SOURCE_CLASS_NAVIGATION,
+ Position = input_bindgen::AINPUT_SOURCE_CLASS_POSITION,
+ Joystick = input_bindgen::AINPUT_SOURCE_CLASS_JOYSTICK,
+}
+
+bitflags! {
+ /// Source of the input device or input events.
+ #[derive(Debug, PartialEq)]
+ pub struct Source: u32 {
+ // Constants from SourceClass, added here for compatibility reasons
+ /// SourceClass::Button
+ const SourceClassButton = SourceClass::Button as u32;
+ /// SourceClass::Pointer
+ const SourceClassPointer = SourceClass::Pointer as u32;
+ /// SourceClass::Navigation
+ const SourceClassNavigation = SourceClass::Navigation as u32;
+ /// SourceClass::Position
+ const SourceClassPosition = SourceClass::Position as u32;
+ /// SourceClass::Joystick
+ const SourceClassJoystick = SourceClass::Joystick as u32;
+
+ /// SOURCE_UNKNOWN
+ const Unknown = input_bindgen::AINPUT_SOURCE_UNKNOWN;
+ /// SOURCE_KEYBOARD
+ const Keyboard = input_bindgen::AINPUT_SOURCE_KEYBOARD;
+ /// SOURCE_DPAD
+ const Dpad = input_bindgen::AINPUT_SOURCE_DPAD;
+ /// SOURCE_GAMEPAD
+ const Gamepad = input_bindgen::AINPUT_SOURCE_GAMEPAD;
+ /// SOURCE_TOUCHSCREEN
+ const Touchscreen = input_bindgen::AINPUT_SOURCE_TOUCHSCREEN;
+ /// SOURCE_MOUSE
+ const Mouse = input_bindgen::AINPUT_SOURCE_MOUSE;
+ /// SOURCE_STYLUS
+ const Stylus = input_bindgen::AINPUT_SOURCE_STYLUS;
+ /// SOURCE_BLUETOOTH_STYLUS
+ const BluetoothStylus = input_bindgen::AINPUT_SOURCE_BLUETOOTH_STYLUS;
+ /// SOURCE_TRACKBALL
+ const Trackball = input_bindgen::AINPUT_SOURCE_TRACKBALL;
+ /// SOURCE_MOUSE_RELATIVE
+ const MouseRelative = input_bindgen::AINPUT_SOURCE_MOUSE_RELATIVE;
+ /// SOURCE_TOUCHPAD
+ const Touchpad = input_bindgen::AINPUT_SOURCE_TOUCHPAD;
+ /// SOURCE_TOUCH_NAVIGATION
+ const TouchNavigation = input_bindgen::AINPUT_SOURCE_TOUCH_NAVIGATION;
+ /// SOURCE_JOYSTICK
+ const Joystick = input_bindgen::AINPUT_SOURCE_JOYSTICK;
+ /// SOURCE_HDMI
+ const HDMI = input_bindgen::AINPUT_SOURCE_HDMI;
+ /// SOURCE_SENSOR
+ const Sensor = input_bindgen::AINPUT_SOURCE_SENSOR;
+ /// SOURCE_ROTARY_ENCODER
+ const RotaryEncoder = input_bindgen::AINPUT_SOURCE_ROTARY_ENCODER;
+ }
+}
+
+/// A rust enum representation of a MotionEvent action.
+#[repr(u32)]
+pub enum MotionAction {
+ /// ACTION_DOWN
+ Down = input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ /// ACTION_UP
+ Up = input_bindgen::AMOTION_EVENT_ACTION_UP,
+ /// ACTION_MOVE
+ Move = input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ /// ACTION_CANCEL
+ Cancel = input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
+ /// ACTION_OUTSIDE
+ Outside = input_bindgen::AMOTION_EVENT_ACTION_OUTSIDE,
+ /// ACTION_POINTER_DOWN
+ PointerDown {
+ /// The index of the affected pointer.
+ action_index: usize,
+ } = input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN,
+ /// ACTION_POINTER_UP
+ PointerUp {
+ /// The index of the affected pointer.
+ action_index: usize,
+ } = input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP,
+ /// ACTION_HOVER_ENTER
+ HoverEnter = input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+ /// ACTION_HOVER_MOVE
+ HoverMove = input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
+ /// ACTION_HOVER_EXIT
+ HoverExit = input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT,
+ /// ACTION_SCROLL
+ Scroll = input_bindgen::AMOTION_EVENT_ACTION_SCROLL,
+ /// ACTION_BUTTON_PRESS
+ ButtonPress = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+ /// ACTION_BUTTON_RELEASE
+ ButtonRelease = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+}
+
+impl fmt::Display for MotionAction {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ MotionAction::Down => write!(f, "DOWN"),
+ MotionAction::Up => write!(f, "UP"),
+ MotionAction::Move => write!(f, "MOVE"),
+ MotionAction::Cancel => write!(f, "CANCEL"),
+ MotionAction::Outside => write!(f, "OUTSIDE"),
+ MotionAction::PointerDown { action_index } => {
+ write!(f, "POINTER_DOWN({})", action_index)
+ }
+ MotionAction::PointerUp { action_index } => write!(f, "POINTER_UP({})", action_index),
+ MotionAction::HoverMove => write!(f, "HOVER_MOVE"),
+ MotionAction::Scroll => write!(f, "SCROLL"),
+ MotionAction::HoverEnter => write!(f, "HOVER_ENTER"),
+ MotionAction::HoverExit => write!(f, "HOVER_EXIT"),
+ MotionAction::ButtonPress => write!(f, "BUTTON_PRESS"),
+ MotionAction::ButtonRelease => write!(f, "BUTTON_RELEASE"),
+ }
+ }
+}
+
+impl From<u32> for MotionAction {
+ fn from(action: u32) -> Self {
+ let (action_masked, action_index) = MotionAction::breakdown_action(action);
+ match action_masked {
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN => MotionAction::Down,
+ input_bindgen::AMOTION_EVENT_ACTION_UP => MotionAction::Up,
+ input_bindgen::AMOTION_EVENT_ACTION_MOVE => MotionAction::Move,
+ input_bindgen::AMOTION_EVENT_ACTION_CANCEL => MotionAction::Cancel,
+ input_bindgen::AMOTION_EVENT_ACTION_OUTSIDE => MotionAction::Outside,
+ input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN => {
+ MotionAction::PointerDown { action_index }
+ }
+ input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP => {
+ MotionAction::PointerUp { action_index }
+ }
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER => MotionAction::HoverEnter,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE => MotionAction::HoverMove,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT => MotionAction::HoverExit,
+ input_bindgen::AMOTION_EVENT_ACTION_SCROLL => MotionAction::Scroll,
+ input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS => MotionAction::ButtonPress,
+ input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE => MotionAction::ButtonRelease,
+ _ => panic!("Unknown action: {}", action),
+ }
+ }
+}
+
+impl MotionAction {
+ fn breakdown_action(action: u32) -> (u32, usize) {
+ let action_masked = action & input_bindgen::AMOTION_EVENT_ACTION_MASK;
+ let index = (action & input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
+ >> input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+ (action_masked, index.try_into().unwrap())
+ }
+}
+
+bitflags! {
+ /// MotionEvent flags.
+ #[derive(Debug)]
+ pub struct MotionFlags: u32 {
+ /// FLAG_CANCELED
+ const CANCELED = input_bindgen::AMOTION_EVENT_FLAG_CANCELED as u32;
+ /// FLAG_WINDOW_IS_OBSCURED
+ const WINDOW_IS_OBSCURED = input_bindgen::AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+ /// FLAG_WINDOW_IS_PARTIALLY_OBSCURED
+ const WINDOW_IS_PARTIALLY_OBSCURED =
+ input_bindgen::AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+ /// FLAG_IS_ACCESSIBILITY_EVENT
+ const IS_ACCESSIBILITY_EVENT =
+ input_bindgen::AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT;
+ /// FLAG_NO_FOCUS_CHANGE
+ const NO_FOCUS_CHANGE = input_bindgen::AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE;
+ }
+}
+
+impl Source {
+ /// Return true if this source is from the provided class
+ pub fn is_from_class(&self, source_class: SourceClass) -> bool {
+ let class_bits = source_class as u32;
+ self.bits() & class_bits == class_bits
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::input::SourceClass;
+ use crate::Source;
+ #[test]
+ fn convert_source_class_pointer() {
+ let source = Source::from_bits(input_bindgen::AINPUT_SOURCE_CLASS_POINTER).unwrap();
+ assert!(source.is_from_class(SourceClass::Pointer));
+ }
+}
diff --git a/libs/input/rust/input_verifier.rs b/libs/input/rust/input_verifier.rs
new file mode 100644
index 0000000000..867af793bc
--- /dev/null
+++ b/libs/input/rust/input_verifier.rs
@@ -0,0 +1,638 @@
+/*
+ * Copyright 2023 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.
+ */
+
+//! Contains the InputVerifier, used to validate a stream of input events.
+
+use crate::ffi::RustPointerProperties;
+use crate::input::{DeviceId, MotionAction, MotionFlags, Source, SourceClass};
+use log::info;
+use std::collections::HashMap;
+use std::collections::HashSet;
+
+fn verify_event(
+ action: MotionAction,
+ pointer_properties: &[RustPointerProperties],
+ flags: &MotionFlags,
+) -> Result<(), String> {
+ let pointer_count = pointer_properties.len();
+ if pointer_count < 1 {
+ return Err(format!("Invalid {} event: no pointers", action));
+ }
+ match action {
+ MotionAction::Down
+ | MotionAction::HoverEnter
+ | MotionAction::HoverExit
+ | MotionAction::HoverMove
+ | MotionAction::Up => {
+ if pointer_count != 1 {
+ return Err(format!(
+ "Invalid {} event: there are {} pointers in the event",
+ action, pointer_count
+ ));
+ }
+ }
+
+ MotionAction::Cancel => {
+ if !flags.contains(MotionFlags::CANCELED) {
+ return Err(format!(
+ "For ACTION_CANCEL, must set FLAG_CANCELED. Received flags: {:#?}",
+ flags
+ ));
+ }
+ }
+
+ MotionAction::PointerDown { action_index } | MotionAction::PointerUp { action_index } => {
+ if action_index >= pointer_count {
+ return Err(format!("Got {}, but event has {} pointer(s)", action, pointer_count));
+ }
+ }
+
+ _ => {}
+ }
+ Ok(())
+}
+
+/// The InputVerifier is used to validate a stream of input events.
+pub struct InputVerifier {
+ name: String,
+ should_log: bool,
+ touching_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
+ hovering_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
+}
+
+impl InputVerifier {
+ /// Create a new InputVerifier.
+ pub fn new(name: &str, should_log: bool) -> Self {
+ logger::init(
+ logger::Config::default()
+ .with_tag_on_device("InputVerifier")
+ .with_min_level(log::Level::Trace),
+ );
+ Self {
+ name: name.to_owned(),
+ should_log,
+ touching_pointer_ids_by_device: HashMap::new(),
+ hovering_pointer_ids_by_device: HashMap::new(),
+ }
+ }
+
+ /// Process a pointer movement event from an InputDevice.
+ /// If the event is not valid, we return an error string that describes the issue.
+ pub fn process_movement(
+ &mut self,
+ device_id: DeviceId,
+ source: Source,
+ action: u32,
+ pointer_properties: &[RustPointerProperties],
+ flags: MotionFlags,
+ ) -> Result<(), String> {
+ if !source.is_from_class(SourceClass::Pointer) {
+ // Skip non-pointer sources like MOUSE_RELATIVE for now
+ return Ok(());
+ }
+ if self.should_log {
+ info!(
+ "Processing {} for device {:?} ({} pointer{}) on {}",
+ MotionAction::from(action).to_string(),
+ device_id,
+ pointer_properties.len(),
+ if pointer_properties.len() == 1 { "" } else { "s" },
+ self.name
+ );
+ }
+
+ verify_event(action.into(), pointer_properties, &flags)?;
+
+ match action.into() {
+ MotionAction::Down => {
+ if self.touching_pointer_ids_by_device.contains_key(&device_id) {
+ return Err(format!(
+ "{}: Invalid DOWN event - pointers already down for device {:?}: {:?}",
+ self.name, device_id, self.touching_pointer_ids_by_device
+ ));
+ }
+ let it = self.touching_pointer_ids_by_device.entry(device_id).or_default();
+ it.insert(pointer_properties[0].id);
+ }
+ MotionAction::PointerDown { action_index } => {
+ if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
+ return Err(format!(
+ "{}: Received POINTER_DOWN but no pointers are currently down \
+ for device {:?}",
+ self.name, device_id
+ ));
+ }
+ let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+ if it.len() != pointer_properties.len() - 1 {
+ return Err(format!(
+ "{}: There are currently {} touching pointers, but the incoming \
+ POINTER_DOWN event has {}",
+ self.name,
+ it.len(),
+ pointer_properties.len()
+ ));
+ }
+ let pointer_id = pointer_properties[action_index].id;
+ if it.contains(&pointer_id) {
+ return Err(format!(
+ "{}: Pointer with id={} already present found in the properties",
+ self.name, pointer_id
+ ));
+ }
+ it.insert(pointer_id);
+ }
+ MotionAction::Move => {
+ if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
+ return Err(format!(
+ "{}: ACTION_MOVE touching pointers don't match",
+ self.name
+ ));
+ }
+ }
+ MotionAction::PointerUp { action_index } => {
+ if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
+ return Err(format!(
+ "{}: ACTION_POINTER_UP touching pointers don't match",
+ self.name
+ ));
+ }
+ let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+ let pointer_id = pointer_properties[action_index].id;
+ it.remove(&pointer_id);
+ }
+ MotionAction::Up => {
+ if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
+ return Err(format!(
+ "{} Received ACTION_UP but no pointers are currently down for device {:?}",
+ self.name, device_id
+ ));
+ }
+ let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+ if it.len() != 1 {
+ return Err(format!(
+ "{}: Got ACTION_UP, but we have pointers: {:?} for device {:?}",
+ self.name, it, device_id
+ ));
+ }
+ let pointer_id = pointer_properties[0].id;
+ if !it.contains(&pointer_id) {
+ return Err(format!(
+ "{}: Got ACTION_UP, but pointerId {} is not touching. Touching pointers:\
+ {:?} for device {:?}",
+ self.name, pointer_id, it, device_id
+ ));
+ }
+ self.touching_pointer_ids_by_device.remove(&device_id);
+ }
+ MotionAction::Cancel => {
+ if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
+ return Err(format!(
+ "{}: Got ACTION_CANCEL, but the pointers don't match. \
+ Existing pointers: {:?}",
+ self.name, self.touching_pointer_ids_by_device
+ ));
+ }
+ self.touching_pointer_ids_by_device.remove(&device_id);
+ }
+ /*
+ * The hovering protocol currently supports a single pointer only, because we do not
+ * have ACTION_HOVER_POINTER_ENTER or ACTION_HOVER_POINTER_EXIT.
+ * Still, we are keeping the infrastructure here pretty general in case that is
+ * eventually supported.
+ */
+ MotionAction::HoverEnter => {
+ if self.hovering_pointer_ids_by_device.contains_key(&device_id) {
+ return Err(format!(
+ "{}: Invalid HOVER_ENTER event - pointers already hovering for device {:?}:\
+ {:?}",
+ self.name, device_id, self.hovering_pointer_ids_by_device
+ ));
+ }
+ let it = self.hovering_pointer_ids_by_device.entry(device_id).or_default();
+ it.insert(pointer_properties[0].id);
+ }
+ MotionAction::HoverMove => {
+ // For compatibility reasons, we allow HOVER_MOVE without a prior HOVER_ENTER.
+ // If there was no prior HOVER_ENTER, just start a new hovering pointer.
+ let it = self.hovering_pointer_ids_by_device.entry(device_id).or_default();
+ it.insert(pointer_properties[0].id);
+ }
+ MotionAction::HoverExit => {
+ if !self.hovering_pointer_ids_by_device.contains_key(&device_id) {
+ return Err(format!(
+ "{}: Invalid HOVER_EXIT event - no pointers are hovering for device {:?}",
+ self.name, device_id
+ ));
+ }
+ let pointer_id = pointer_properties[0].id;
+ let it = self.hovering_pointer_ids_by_device.get_mut(&device_id).unwrap();
+ it.remove(&pointer_id);
+
+ if !it.is_empty() {
+ return Err(format!(
+ "{}: Removed hovering pointer {}, but pointers are still\
+ hovering for device {:?}: {:?}",
+ self.name, pointer_id, device_id, it
+ ));
+ }
+ self.hovering_pointer_ids_by_device.remove(&device_id);
+ }
+ _ => return Ok(()),
+ }
+ Ok(())
+ }
+
+ /// Notify the verifier that the device has been reset, which will cause the verifier to erase
+ /// the current internal state for this device. Subsequent events from this device are expected
+ //// to start a new gesture.
+ pub fn reset_device(&mut self, device_id: DeviceId) {
+ self.touching_pointer_ids_by_device.remove(&device_id);
+ self.hovering_pointer_ids_by_device.remove(&device_id);
+ }
+
+ fn ensure_touching_pointers_match(
+ &self,
+ device_id: DeviceId,
+ pointer_properties: &[RustPointerProperties],
+ ) -> bool {
+ let Some(pointers) = self.touching_pointer_ids_by_device.get(&device_id) else {
+ return false;
+ };
+
+ if pointers.len() != pointer_properties.len() {
+ return false;
+ }
+
+ for pointer_property in pointer_properties.iter() {
+ let pointer_id = pointer_property.id;
+ if !pointers.contains(&pointer_id) {
+ return false;
+ }
+ }
+ true
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::input_verifier::InputVerifier;
+ use crate::DeviceId;
+ use crate::MotionFlags;
+ use crate::RustPointerProperties;
+ use crate::Source;
+
+ #[test]
+ /**
+ * Send a DOWN event with 2 pointers and ensure that it's marked as invalid.
+ */
+ fn bad_down_event() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ true);
+ let pointer_properties =
+ Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_err());
+ }
+
+ #[test]
+ fn single_pointer_stream() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_UP,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ }
+
+ #[test]
+ fn two_pointer_stream() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ // POINTER 1 DOWN
+ let two_pointer_properties =
+ Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN
+ | (1 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ &two_pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ // POINTER 0 UP
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP
+ | (0 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ &two_pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ // ACTION_UP for pointer id=1
+ let pointer_1_properties = Vec::from([RustPointerProperties { id: 1 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_UP,
+ &pointer_1_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ }
+
+ #[test]
+ fn multi_device_stream() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(2),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(2),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_UP,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ }
+
+ #[test]
+ fn action_cancel() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
+ &pointer_properties,
+ MotionFlags::CANCELED,
+ )
+ .is_ok());
+ }
+
+ #[test]
+ fn invalid_action_cancel() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
+ &pointer_properties,
+ MotionFlags::empty(), // forgot to set FLAG_CANCELED
+ )
+ .is_err());
+ }
+
+ #[test]
+ fn invalid_up() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_UP,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_err());
+ }
+
+ #[test]
+ fn correct_hover_sequence() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ }
+
+ #[test]
+ fn double_hover_enter() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_err());
+ }
+
+ // Send a MOVE without a preceding DOWN event. This is OK because it's from source
+ // MOUSE_RELATIVE, which is used during pointer capture. The verifier should allow such event.
+ #[test]
+ fn relative_mouse_move() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(2),
+ Source::MouseRelative,
+ input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ }
+
+ // Send a MOVE event with incorrect number of pointers (one of the pointers is missing).
+ #[test]
+ fn move_with_wrong_number_of_pointers() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ // POINTER 1 DOWN
+ let two_pointer_properties =
+ Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN
+ | (1 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ &two_pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ // MOVE event with 1 pointer missing (the pointer with id = 1). It should be rejected
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_err());
+ }
+}
diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs
new file mode 100644
index 0000000000..01d959942c
--- /dev/null
+++ b/libs/input/rust/lib.rs
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2023 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.
+ */
+
+//! The rust component of libinput.
+
+mod input;
+mod input_verifier;
+
+pub use input::{DeviceId, MotionAction, MotionFlags, Source};
+pub use input_verifier::InputVerifier;
+
+#[cxx::bridge(namespace = "android::input")]
+#[allow(unsafe_op_in_unsafe_fn)]
+mod ffi {
+ #[namespace = "android"]
+ unsafe extern "C++" {
+ include!("ffi/FromRustToCpp.h");
+ fn shouldLog(tag: &str) -> bool;
+ }
+
+ #[namespace = "android::input::verifier"]
+ extern "Rust" {
+ /// Used to validate the incoming motion stream.
+ /// This class is not thread-safe.
+ /// State is stored in the "InputVerifier" object
+ /// that can be created via the 'create' method.
+ /// Usage:
+ ///
+ /// ```ignore
+ /// Box<InputVerifier> verifier = create("inputChannel name");
+ /// result = process_movement(verifier, ...);
+ /// if (result) {
+ /// crash(result.error_message());
+ /// }
+ /// ```
+ type InputVerifier;
+ fn create(name: String) -> Box<InputVerifier>;
+ fn process_movement(
+ verifier: &mut InputVerifier,
+ device_id: i32,
+ source: u32,
+ action: u32,
+ pointer_properties: &[RustPointerProperties],
+ flags: u32,
+ ) -> String;
+ fn reset_device(verifier: &mut InputVerifier, device_id: i32);
+ }
+
+ #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+ pub struct RustPointerProperties {
+ pub id: i32,
+ }
+}
+
+use crate::ffi::RustPointerProperties;
+
+fn create(name: String) -> Box<InputVerifier> {
+ Box::new(InputVerifier::new(&name, ffi::shouldLog("InputVerifierLogEvents")))
+}
+
+fn process_movement(
+ verifier: &mut InputVerifier,
+ device_id: i32,
+ source: u32,
+ action: u32,
+ pointer_properties: &[RustPointerProperties],
+ flags: u32,
+) -> String {
+ let result = verifier.process_movement(
+ DeviceId(device_id),
+ Source::from_bits(source).unwrap(),
+ action,
+ pointer_properties,
+ MotionFlags::from_bits(flags).unwrap(),
+ );
+ match result {
+ Ok(()) => "".to_string(),
+ Err(e) => e,
+ }
+}
+
+fn reset_device(verifier: &mut InputVerifier, device_id: i32) {
+ verifier.reset_device(DeviceId(device_id));
+}
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 3fc7d9d568..13cfb491b5 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -63,6 +63,7 @@ cc_test {
"libPlatformProperties",
"libtinyxml2",
"libutils",
+ "server_configurable_flags",
],
data: [
"data/*",
diff --git a/libs/input/tests/InputDevice_test.cpp b/libs/input/tests/InputDevice_test.cpp
index 5c44366611..fe5490caf5 100644
--- a/libs/input/tests/InputDevice_test.cpp
+++ b/libs/input/tests/InputDevice_test.cpp
@@ -20,6 +20,7 @@
#include <input/InputDevice.h>
#include <input/KeyLayoutMap.h>
#include <input/Keyboard.h>
+#include <linux/uinput.h>
#include "android-base/file.h"
namespace android {
@@ -97,7 +98,7 @@ TEST_F(InputDeviceKeyMapTest, keyCharacterMapWithOverlayParcelingTest) {
ASSERT_EQ(*map, *mKeyMap.keyCharacterMap);
}
-TEST_F(InputDeviceKeyMapTest, keyCharacteMapApplyMultipleOverlaysTest) {
+TEST_F(InputDeviceKeyMapTest, keyCharacterMapApplyMultipleOverlaysTest) {
std::string frenchOverlayPath = base::GetExecutableDirectory() + "/data/french.kcm";
std::string englishOverlayPath = base::GetExecutableDirectory() + "/data/english_us.kcm";
std::string germanOverlayPath = base::GetExecutableDirectory() + "/data/german.kcm";
@@ -133,14 +134,33 @@ TEST_F(InputDeviceKeyMapTest, keyCharacteMapApplyMultipleOverlaysTest) {
ASSERT_EQ(*mKeyMap.keyCharacterMap, *frenchOverlaidKeyCharacterMap);
}
-TEST_F(InputDeviceKeyMapTest, keyCharacteMapBadAxisLabel) {
+TEST_F(InputDeviceKeyMapTest, keyCharacterMapApplyOverlayTest) {
+ std::string frenchOverlayPath = base::GetExecutableDirectory() + "/data/french.kcm";
+ base::Result<std::shared_ptr<KeyCharacterMap>> frenchOverlay =
+ KeyCharacterMap::load(frenchOverlayPath, KeyCharacterMap::Format::OVERLAY);
+ ASSERT_TRUE(frenchOverlay.ok()) << "Cannot load KeyCharacterMap at " << frenchOverlayPath;
+
+ // Apply the French overlay
+ mKeyMap.keyCharacterMap->combine(*frenchOverlay->get());
+
+ // Check if mapping for key_Q is correct
+ int32_t outKeyCode;
+ status_t mapKeyResult = mKeyMap.keyCharacterMap->mapKey(KEY_Q, /*usageCode=*/0, &outKeyCode);
+ ASSERT_EQ(mapKeyResult, OK) << "No mapping for KEY_Q for " << frenchOverlayPath;
+ ASSERT_EQ(outKeyCode, AKEYCODE_A);
+
+ mapKeyResult = mKeyMap.keyCharacterMap->mapKey(KEY_E, /*usageCode=*/0, &outKeyCode);
+ ASSERT_NE(mapKeyResult, OK) << "Mapping exists for KEY_E for " << frenchOverlayPath;
+}
+
+TEST_F(InputDeviceKeyMapTest, keyCharacterMapBadAxisLabel) {
std::string klPath = base::GetExecutableDirectory() + "/data/bad_axis_label.kl";
base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(klPath);
ASSERT_FALSE(ret.ok()) << "Should not be able to load KeyLayout at " << klPath;
}
-TEST_F(InputDeviceKeyMapTest, keyCharacteMapBadLedLabel) {
+TEST_F(InputDeviceKeyMapTest, keyCharacterMapBadLedLabel) {
std::string klPath = base::GetExecutableDirectory() + "/data/bad_led_label.kl";
base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(klPath);
diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp
index 3ecf8eed50..06b841be0d 100644
--- a/libs/input/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp
@@ -25,7 +25,22 @@ using android::base::Result;
namespace android {
-constexpr static float EPSILON = MotionEvent::ROUNDING_PRECISION;
+namespace {
+
+static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
+static constexpr int32_t POINTER_1_DOWN =
+ AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr int32_t POINTER_2_DOWN =
+ AMOTION_EVENT_ACTION_POINTER_DOWN | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+struct Pointer {
+ int32_t id;
+ float x;
+ float y;
+ bool isResampled = false;
+};
+
+} // namespace
class InputPublisherAndConsumerTest : public testing::Test {
protected:
@@ -46,12 +61,28 @@ protected:
mConsumer = std::make_unique<InputConsumer>(mClientChannel);
}
- void PublishAndConsumeKeyEvent();
- void PublishAndConsumeMotionEvent();
- void PublishAndConsumeFocusEvent();
- void PublishAndConsumeCaptureEvent();
- void PublishAndConsumeDragEvent();
- void PublishAndConsumeTouchModeEvent();
+ void publishAndConsumeKeyEvent();
+ void publishAndConsumeMotionStream();
+ void publishAndConsumeFocusEvent();
+ void publishAndConsumeCaptureEvent();
+ void publishAndConsumeDragEvent();
+ void publishAndConsumeTouchModeEvent();
+ void publishAndConsumeMotionEvent(int32_t action, nsecs_t downTime,
+ const std::vector<Pointer>& pointers);
+
+private:
+ // The sequence number to use when publishing the next event
+ uint32_t mSeq = 1;
+
+ void publishAndConsumeMotionEvent(
+ int32_t deviceId, uint32_t source, int32_t displayId, std::array<uint8_t, 32> hmac,
+ int32_t action, int32_t actionButton, int32_t flags, int32_t edgeFlags,
+ int32_t metaState, int32_t buttonState, MotionClassification classification,
+ float xScale, float yScale, float xOffset, float yOffset, float xPrecision,
+ float yPrecision, float xCursorPosition, float yCursorPosition, float rawXScale,
+ float rawYScale, float rawXOffset, float rawYOffset, nsecs_t downTime,
+ nsecs_t eventTime, const std::vector<PointerProperties>& pointerProperties,
+ const std::vector<PointerCoords>& pointerCoords);
};
TEST_F(InputPublisherAndConsumerTest, GetChannel_ReturnsTheChannel) {
@@ -63,10 +94,10 @@ TEST_F(InputPublisherAndConsumerTest, GetChannel_ReturnsTheChannel) {
mConsumer->getChannel()->getConnectionToken());
}
-void InputPublisherAndConsumerTest::PublishAndConsumeKeyEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeKeyEvent() {
status_t status;
- constexpr uint32_t seq = 15;
+ const uint32_t seq = mSeq++;
int32_t eventId = InputEvent::nextId();
constexpr int32_t deviceId = 1;
constexpr uint32_t source = AINPUT_SOURCE_KEYBOARD;
@@ -132,20 +163,43 @@ void InputPublisherAndConsumerTest::PublishAndConsumeKeyEvent() {
<< "finished signal's consume time should be greater than publish time";
}
-void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() {
- status_t status;
+void InputPublisherAndConsumerTest::publishAndConsumeMotionStream() {
+ const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
- constexpr uint32_t seq = 15;
- int32_t eventId = InputEvent::nextId();
+ publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30}});
+
+ publishAndConsumeMotionEvent(POINTER_1_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300}});
+
+ publishAndConsumeMotionEvent(POINTER_2_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300},
+ Pointer{.id = 2, .x = 300, .y = 400}});
+
+ // Provide a consistent input stream - cancel the gesture that was started above
+ publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_CANCEL, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300},
+ Pointer{.id = 2, .x = 300, .y = 400}});
+}
+
+void InputPublisherAndConsumerTest::publishAndConsumeMotionEvent(
+ int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers) {
constexpr int32_t deviceId = 1;
constexpr uint32_t source = AINPUT_SOURCE_TOUCHSCREEN;
constexpr int32_t displayId = ADISPLAY_ID_DEFAULT;
constexpr std::array<uint8_t, 32> hmac = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
- constexpr int32_t action = AMOTION_EVENT_ACTION_MOVE;
constexpr int32_t actionButton = 0;
- constexpr int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+ int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+
+ if (action == AMOTION_EVENT_ACTION_CANCEL) {
+ flags |= AMOTION_EVENT_FLAG_CANCELED;
+ }
+ const size_t pointerCount = pointers.size();
constexpr int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP;
constexpr int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
constexpr int32_t buttonState = AMOTION_EVENT_BUTTON_PRIMARY;
@@ -162,20 +216,21 @@ void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() {
constexpr float yPrecision = 0.5;
constexpr float xCursorPosition = 1.3;
constexpr float yCursorPosition = 50.6;
- constexpr nsecs_t downTime = 3;
- constexpr size_t pointerCount = 3;
- constexpr nsecs_t eventTime = 4;
- const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
- PointerProperties pointerProperties[pointerCount];
- PointerCoords pointerCoords[pointerCount];
+
+ const nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ std::vector<PointerProperties> pointerProperties;
+ std::vector<PointerCoords> pointerCoords;
for (size_t i = 0; i < pointerCount; i++) {
+ pointerProperties.push_back({});
pointerProperties[i].clear();
- pointerProperties[i].id = (i + 2) % pointerCount;
+ pointerProperties[i].id = pointers[i].id;
pointerProperties[i].toolType = ToolType::FINGER;
+ pointerCoords.push_back({});
pointerCoords[i].clear();
- pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, 100 * i);
- pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, 200 * i);
+ pointerCoords[i].isResampled = pointers[i].isResampled;
+ pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, pointers[i].x);
+ pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, pointers[i].y);
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.5 * i);
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.7 * i);
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 1.5 * i);
@@ -185,18 +240,40 @@ void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() {
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 3.5 * i);
}
+ publishAndConsumeMotionEvent(deviceId, source, displayId, hmac, action, actionButton, flags,
+ edgeFlags, metaState, buttonState, classification, xScale, yScale,
+ xOffset, yOffset, xPrecision, yPrecision, xCursorPosition,
+ yCursorPosition, rawXScale, rawYScale, rawXOffset, rawYOffset,
+ downTime, eventTime, pointerProperties, pointerCoords);
+}
+
+void InputPublisherAndConsumerTest::publishAndConsumeMotionEvent(
+ int32_t deviceId, uint32_t source, int32_t displayId, std::array<uint8_t, 32> hmac,
+ int32_t action, int32_t actionButton, int32_t flags, int32_t edgeFlags, int32_t metaState,
+ int32_t buttonState, MotionClassification classification, float xScale, float yScale,
+ float xOffset, float yOffset, float xPrecision, float yPrecision, float xCursorPosition,
+ float yCursorPosition, float rawXScale, float rawYScale, float rawXOffset, float rawYOffset,
+ nsecs_t downTime, nsecs_t eventTime,
+ const std::vector<PointerProperties>& pointerProperties,
+ const std::vector<PointerCoords>& pointerCoords) {
+ const uint32_t seq = mSeq++;
+ const int32_t eventId = InputEvent::nextId();
ui::Transform transform;
transform.set({xScale, 0, xOffset, 0, yScale, yOffset, 0, 0, 1});
ui::Transform rawTransform;
rawTransform.set({rawXScale, 0, rawXOffset, 0, rawYScale, rawYOffset, 0, 0, 1});
+
+ status_t status;
+ ASSERT_EQ(pointerProperties.size(), pointerCoords.size());
+ const size_t pointerCount = pointerProperties.size();
+ const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
status = mPublisher->publishMotionEvent(seq, eventId, deviceId, source, displayId, hmac, action,
actionButton, flags, edgeFlags, metaState, buttonState,
classification, transform, xPrecision, yPrecision,
xCursorPosition, yCursorPosition, rawTransform,
- downTime, eventTime, pointerCount, pointerProperties,
- pointerCoords);
- ASSERT_EQ(OK, status)
- << "publisher publishMotionEvent should return OK";
+ downTime, eventTime, pointerCount,
+ pointerProperties.data(), pointerCoords.data());
+ ASSERT_EQ(OK, status) << "publisher publishMotionEvent should return OK";
uint32_t consumeSeq;
InputEvent* event;
@@ -280,7 +357,7 @@ void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() {
<< "finished signal's consume time should be greater than publish time";
}
-void InputPublisherAndConsumerTest::PublishAndConsumeFocusEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeFocusEvent() {
status_t status;
constexpr uint32_t seq = 15;
@@ -321,7 +398,7 @@ void InputPublisherAndConsumerTest::PublishAndConsumeFocusEvent() {
<< "finished signal's consume time should be greater than publish time";
}
-void InputPublisherAndConsumerTest::PublishAndConsumeCaptureEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeCaptureEvent() {
status_t status;
constexpr uint32_t seq = 42;
@@ -361,7 +438,7 @@ void InputPublisherAndConsumerTest::PublishAndConsumeCaptureEvent() {
<< "finished signal's consume time should be greater than publish time";
}
-void InputPublisherAndConsumerTest::PublishAndConsumeDragEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeDragEvent() {
status_t status;
constexpr uint32_t seq = 15;
@@ -405,7 +482,7 @@ void InputPublisherAndConsumerTest::PublishAndConsumeDragEvent() {
<< "finished signal's consume time should be greater than publish time";
}
-void InputPublisherAndConsumerTest::PublishAndConsumeTouchModeEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeTouchModeEvent() {
status_t status;
constexpr uint32_t seq = 15;
@@ -462,27 +539,27 @@ TEST_F(InputPublisherAndConsumerTest, SendTimeline) {
}
TEST_F(InputPublisherAndConsumerTest, PublishKeyEvent_EndToEnd) {
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
}
TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_EndToEnd) {
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeMotionStream());
}
TEST_F(InputPublisherAndConsumerTest, PublishFocusEvent_EndToEnd) {
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeFocusEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent());
}
TEST_F(InputPublisherAndConsumerTest, PublishCaptureEvent_EndToEnd) {
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeCaptureEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeCaptureEvent());
}
TEST_F(InputPublisherAndConsumerTest, PublishDragEvent_EndToEnd) {
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeDragEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeDragEvent());
}
TEST_F(InputPublisherAndConsumerTest, PublishTouchModeEvent_EndToEnd) {
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeTouchModeEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent());
}
TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenSequenceNumberIsZero_ReturnsError) {
@@ -546,17 +623,29 @@ TEST_F(InputPublisherAndConsumerTest,
}
TEST_F(InputPublisherAndConsumerTest, PublishMultipleEvents_EndToEnd) {
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeFocusEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeCaptureEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeDragEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeTouchModeEvent());
+ const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30}});
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+ publishAndConsumeMotionEvent(POINTER_1_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300}});
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent());
+ publishAndConsumeMotionEvent(POINTER_2_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300},
+ Pointer{.id = 2, .x = 200, .y = 300}});
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeCaptureEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeDragEvent());
+ // Provide a consistent input stream - cancel the gesture that was started above
+ publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_CANCEL, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300},
+ Pointer{.id = 2, .x = 200, .y = 300}});
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent());
}
} // namespace android
diff --git a/libs/input/tests/InputVerifier_test.cpp b/libs/input/tests/InputVerifier_test.cpp
index e24fa6ed0b..e2eb08096b 100644
--- a/libs/input/tests/InputVerifier_test.cpp
+++ b/libs/input/tests/InputVerifier_test.cpp
@@ -20,10 +20,35 @@
namespace android {
+using android::base::Result;
+
TEST(InputVerifierTest, CreationWithInvalidUtfStringDoesNotCrash) {
constexpr char bytes[] = {static_cast<char>(0xC0), static_cast<char>(0x80)};
const std::string name(bytes, sizeof(bytes));
InputVerifier verifier(name);
}
+TEST(InputVerifierTest, ProcessSourceClassPointer) {
+ InputVerifier verifier("Verify testOnTouchEventScroll");
+
+ std::vector<PointerProperties> properties;
+ properties.push_back({});
+ properties.back().clear();
+ properties.back().id = 0;
+ properties.back().toolType = ToolType::UNKNOWN;
+
+ std::vector<PointerCoords> coords;
+ coords.push_back({});
+ coords.back().clear();
+ coords.back().setAxisValue(AMOTION_EVENT_AXIS_X, 75);
+ coords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, 300);
+
+ const Result<void> result =
+ verifier.processMovement(/*deviceId=*/0, AINPUT_SOURCE_CLASS_POINTER,
+ AMOTION_EVENT_ACTION_DOWN,
+ /*pointerCount=*/properties.size(), properties.data(),
+ coords.data(), /*flags=*/0);
+ ASSERT_TRUE(result.ok());
+}
+
} // namespace android
diff --git a/libs/input/tests/MotionPredictorMetricsManager_test.cpp b/libs/input/tests/MotionPredictorMetricsManager_test.cpp
index b420a5a4e7..31cc1459fc 100644
--- a/libs/input/tests/MotionPredictorMetricsManager_test.cpp
+++ b/libs/input/tests/MotionPredictorMetricsManager_test.cpp
@@ -39,6 +39,7 @@ using ::testing::Matches;
using GroundTruthPoint = MotionPredictorMetricsManager::GroundTruthPoint;
using PredictionPoint = MotionPredictorMetricsManager::PredictionPoint;
using AtomFields = MotionPredictorMetricsManager::AtomFields;
+using ReportAtomFunction = MotionPredictorMetricsManager::ReportAtomFunction;
inline constexpr int NANOS_PER_MILLIS = 1'000'000;
@@ -664,9 +665,16 @@ TEST(ErrorComputationHelperTest, ComputePressureRmsesSimpleTest) {
// --- MotionPredictorMetricsManager tests. ---
-// Helper function that instantiates a MetricsManager with the given mock logged AtomFields. Takes
-// vectors of ground truth and prediction points of the same length, and passes these points to the
-// MetricsManager. The format of these vectors is expected to be:
+// Creates a mock atom reporting function that appends the reported atom to the given vector.
+ReportAtomFunction createMockReportAtomFunction(std::vector<AtomFields>& reportedAtomFields) {
+ return [&reportedAtomFields](const AtomFields& atomFields) -> void {
+ reportedAtomFields.push_back(atomFields);
+ };
+}
+
+// Helper function that instantiates a MetricsManager that reports metrics to outReportedAtomFields.
+// Takes vectors of ground truth and prediction points of the same length, and passes these points
+// to the MetricsManager. The format of these vectors is expected to be:
// • groundTruthPoints: chronologically-ordered ground truth points, with at least 2 elements.
// • predictionPoints: the first index points to a vector of predictions corresponding to the
// source ground truth point with the same index.
@@ -678,15 +686,16 @@ TEST(ErrorComputationHelperTest, ComputePressureRmsesSimpleTest) {
// prediction sets (that is, excluding the first and last). Thus, groundTruthPoints and
// predictionPoints should have size at least TEST_MAX_NUM_PREDICTIONS + 2.
//
-// The passed-in outAtomFields will contain the logged AtomFields when the function returns.
+// When the function returns, outReportedAtomFields will contain the reported AtomFields.
//
// This function returns void so that it can use test assertions.
void runMetricsManager(const std::vector<GroundTruthPoint>& groundTruthPoints,
const std::vector<std::vector<PredictionPoint>>& predictionPoints,
- std::vector<AtomFields>& outAtomFields) {
+ std::vector<AtomFields>& outReportedAtomFields) {
MotionPredictorMetricsManager metricsManager(TEST_PREDICTION_INTERVAL_NANOS,
- TEST_MAX_NUM_PREDICTIONS);
- metricsManager.setMockLoggedAtomFields(&outAtomFields);
+ TEST_MAX_NUM_PREDICTIONS,
+ createMockReportAtomFunction(
+ outReportedAtomFields));
// Validate structure of groundTruthPoints and predictionPoints.
ASSERT_EQ(predictionPoints.size(), groundTruthPoints.size());
@@ -712,18 +721,18 @@ void runMetricsManager(const std::vector<GroundTruthPoint>& groundTruthPoints,
// • Input: no prediction data.
// • Expectation: no metrics should be logged.
TEST(MotionPredictorMetricsManagerTest, NoPredictions) {
- std::vector<AtomFields> mockLoggedAtomFields;
+ std::vector<AtomFields> reportedAtomFields;
MotionPredictorMetricsManager metricsManager(TEST_PREDICTION_INTERVAL_NANOS,
- TEST_MAX_NUM_PREDICTIONS);
- metricsManager.setMockLoggedAtomFields(&mockLoggedAtomFields);
+ TEST_MAX_NUM_PREDICTIONS,
+ createMockReportAtomFunction(reportedAtomFields));
metricsManager.onRecord(makeMotionEvent(
GroundTruthPoint{{.position = Eigen::Vector2f(0, 0), .pressure = 0}, .timestamp = 0}));
metricsManager.onRecord(makeLiftMotionEvent());
- // Check that mockLoggedAtomFields is still empty (as it was initialized empty), ensuring that
+ // Check that reportedAtomFields is still empty (as it was initialized empty), ensuring that
// no metrics were logged.
- EXPECT_EQ(0u, mockLoggedAtomFields.size());
+ EXPECT_EQ(0u, reportedAtomFields.size());
}
// Perfect predictions test:
@@ -744,14 +753,14 @@ TEST(MotionPredictorMetricsManagerTest, ConstantGroundTruthPerfectPredictions) {
groundTruthPoint.timestamp += TEST_PREDICTION_INTERVAL_NANOS;
}
- std::vector<AtomFields> atomFields;
- runMetricsManager(groundTruthPoints, predictionPoints, atomFields);
+ std::vector<AtomFields> reportedAtomFields;
+ runMetricsManager(groundTruthPoints, predictionPoints, reportedAtomFields);
- ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, atomFields.size());
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, reportedAtomFields.size());
// Check that errors are all zero, or NO_DATA_SENTINEL for unreported metrics.
- for (size_t i = 0; i < atomFields.size(); ++i) {
+ for (size_t i = 0; i < reportedAtomFields.size(); ++i) {
SCOPED_TRACE(testing::Message() << "i = " << i);
- const AtomFields& atom = atomFields[i];
+ const AtomFields& atom = reportedAtomFields[i];
const nsecs_t deltaTimeBucketNanos = TEST_PREDICTION_INTERVAL_NANOS * (i + 1);
EXPECT_EQ(deltaTimeBucketNanos / NANOS_PER_MILLIS, atom.deltaTimeBucketMilliseconds);
// General errors: reported for every time bucket.
@@ -764,7 +773,7 @@ TEST(MotionPredictorMetricsManagerTest, ConstantGroundTruthPerfectPredictions) {
EXPECT_EQ(NO_DATA_SENTINEL, atom.highVelocityAlongTrajectoryRmse);
EXPECT_EQ(NO_DATA_SENTINEL, atom.highVelocityOffTrajectoryRmse);
// Scale-invariant errors: reported only for the last time bucket.
- if (i + 1 == atomFields.size()) {
+ if (i + 1 == reportedAtomFields.size()) {
EXPECT_EQ(0, atom.scaleInvariantAlongTrajectoryRmse);
EXPECT_EQ(0, atom.scaleInvariantOffTrajectoryRmse);
} else {
@@ -801,14 +810,14 @@ TEST(MotionPredictorMetricsManagerTest, QuadraticPressureLinearPredictions) {
computePressureRmses(groundTruthPoints, predictionPoints);
// Run test.
- std::vector<AtomFields> atomFields;
- runMetricsManager(groundTruthPoints, predictionPoints, atomFields);
+ std::vector<AtomFields> reportedAtomFields;
+ runMetricsManager(groundTruthPoints, predictionPoints, reportedAtomFields);
// Check logged metrics match expectations.
- ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, atomFields.size());
- for (size_t i = 0; i < atomFields.size(); ++i) {
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, reportedAtomFields.size());
+ for (size_t i = 0; i < reportedAtomFields.size(); ++i) {
SCOPED_TRACE(testing::Message() << "i = " << i);
- const AtomFields& atom = atomFields[i];
+ const AtomFields& atom = reportedAtomFields[i];
// Check time bucket delta matches expectation based on index and prediction interval.
const nsecs_t deltaTimeBucketNanos = TEST_PREDICTION_INTERVAL_NANOS * (i + 1);
EXPECT_EQ(deltaTimeBucketNanos / NANOS_PER_MILLIS, atom.deltaTimeBucketMilliseconds);
@@ -845,14 +854,14 @@ TEST(MotionPredictorMetricsManagerTest, QuadraticPositionLinearPredictionsGenera
computeGeneralPositionErrors(groundTruthPoints, predictionPoints);
// Run test.
- std::vector<AtomFields> atomFields;
- runMetricsManager(groundTruthPoints, predictionPoints, atomFields);
+ std::vector<AtomFields> reportedAtomFields;
+ runMetricsManager(groundTruthPoints, predictionPoints, reportedAtomFields);
// Check logged metrics match expectations.
- ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, atomFields.size());
- for (size_t i = 0; i < atomFields.size(); ++i) {
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, reportedAtomFields.size());
+ for (size_t i = 0; i < reportedAtomFields.size(); ++i) {
SCOPED_TRACE(testing::Message() << "i = " << i);
- const AtomFields& atom = atomFields[i];
+ const AtomFields& atom = reportedAtomFields[i];
// Check time bucket delta matches expectation based on index and prediction interval.
const nsecs_t deltaTimeBucketNanos = TEST_PREDICTION_INTERVAL_NANOS * (i + 1);
EXPECT_EQ(deltaTimeBucketNanos / NANOS_PER_MILLIS, atom.deltaTimeBucketMilliseconds);
@@ -896,14 +905,14 @@ TEST(MotionPredictorMetricsManagerTest, CounterclockwiseOctagonGroundTruthLinear
computeGeneralPositionErrors(groundTruthPoints, predictionPoints);
// Run test.
- std::vector<AtomFields> atomFields;
- runMetricsManager(groundTruthPoints, predictionPoints, atomFields);
+ std::vector<AtomFields> reportedAtomFields;
+ runMetricsManager(groundTruthPoints, predictionPoints, reportedAtomFields);
// Check logged metrics match expectations.
- ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, atomFields.size());
- for (size_t i = 0; i < atomFields.size(); ++i) {
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, reportedAtomFields.size());
+ for (size_t i = 0; i < reportedAtomFields.size(); ++i) {
SCOPED_TRACE(testing::Message() << "i = " << i);
- const AtomFields& atom = atomFields[i];
+ const AtomFields& atom = reportedAtomFields[i];
const nsecs_t deltaTimeBucketNanos = TEST_PREDICTION_INTERVAL_NANOS * (i + 1);
EXPECT_EQ(deltaTimeBucketNanos / NANOS_PER_MILLIS, atom.deltaTimeBucketMilliseconds);
@@ -926,7 +935,7 @@ TEST(MotionPredictorMetricsManagerTest, CounterclockwiseOctagonGroundTruthLinear
// to general errors (where reported).
//
// As above, use absolute value for RMSE, since it must be non-negative.
- if (i + 2 >= atomFields.size()) {
+ if (i + 2 >= reportedAtomFields.size()) {
EXPECT_NEAR(static_cast<int>(
1000 * std::abs(generalPositionErrors[i].alongTrajectoryErrorMean)),
atom.highVelocityAlongTrajectoryRmse, 1);
@@ -946,7 +955,7 @@ TEST(MotionPredictorMetricsManagerTest, CounterclockwiseOctagonGroundTruthLinear
// to scale-invariant errors by dividing by `strokeVelocty * TEST_MAX_NUM_PREDICTIONS`.
//
// As above, use absolute value for RMSE, since it must be non-negative.
- if (i + 1 == atomFields.size()) {
+ if (i + 1 == reportedAtomFields.size()) {
const float pathLength = strokeVelocity * TEST_MAX_NUM_PREDICTIONS;
std::vector<float> alongTrajectoryAbsoluteErrors;
std::vector<float> offTrajectoryAbsoluteErrors;
diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp
index 4ac7ae920e..33431146ea 100644
--- a/libs/input/tests/MotionPredictor_test.cpp
+++ b/libs/input/tests/MotionPredictor_test.cpp
@@ -147,4 +147,35 @@ TEST(MotionPredictorTest, FlagDisablesPrediction) {
ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN));
}
+using AtomFields = MotionPredictorMetricsManager::AtomFields;
+using ReportAtomFunction = MotionPredictorMetricsManager::ReportAtomFunction;
+
+// Creates a mock atom reporting function that appends the reported atom to the given vector.
+// The passed-in pointer must not be nullptr.
+ReportAtomFunction createMockReportAtomFunction(std::vector<AtomFields>* reportedAtomFields) {
+ return [reportedAtomFields](const AtomFields& atomFields) -> void {
+ reportedAtomFields->push_back(atomFields);
+ };
+}
+
+TEST(MotionPredictorMetricsManagerIntegrationTest, ReportsMetrics) {
+ std::vector<AtomFields> reportedAtomFields;
+ MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
+ []() { return true /*enable prediction*/; },
+ createMockReportAtomFunction(&reportedAtomFields));
+
+ ASSERT_TRUE(predictor.record(getMotionEvent(DOWN, 1, 1, 0ms, /*deviceId=*/0)).ok());
+ ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 2, 2, 4ms, /*deviceId=*/0)).ok());
+ ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 3, 3, 8ms, /*deviceId=*/0)).ok());
+ ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 4, 4, 12ms, /*deviceId=*/0)).ok());
+ ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 5, 5, 16ms, /*deviceId=*/0)).ok());
+ ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 6, 6, 20ms, /*deviceId=*/0)).ok());
+ ASSERT_TRUE(predictor.record(getMotionEvent(UP, 7, 7, 24ms, /*deviceId=*/0)).ok());
+
+ // The number of atoms reported should equal the number of prediction time buckets, which is
+ // given by the prediction model's output length. For now, this value is always 5, and we
+ // hardcode it because it's not publicly accessible from the MotionPredictor.
+ EXPECT_EQ(5u, reportedAtomFields.size());
+}
+
} // namespace android
diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp
index 655de803ae..1cb7f7ba8c 100644
--- a/libs/input/tests/TouchResampling_test.cpp
+++ b/libs/input/tests/TouchResampling_test.cpp
@@ -27,10 +27,13 @@ using namespace std::chrono_literals;
namespace android {
+namespace {
+
struct Pointer {
int32_t id;
float x;
float y;
+ ToolType toolType = ToolType::FINGER;
bool isResampled = false;
};
@@ -40,6 +43,8 @@ struct InputEventEntry {
int32_t action;
};
+} // namespace
+
class TouchResamplingTest : public testing::Test {
protected:
std::unique_ptr<InputPublisher> mPublisher;
@@ -99,7 +104,7 @@ void TouchResamplingTest::publishSimpleMotionEvent(int32_t action, nsecs_t event
properties.push_back({});
properties.back().clear();
properties.back().id = pointer.id;
- properties.back().toolType = ToolType::FINGER;
+ properties.back().toolType = pointer.toolType;
coords.push_back({});
coords.back().clear();
@@ -292,6 +297,48 @@ TEST_F(TouchResamplingTest, EventIsResampledWithDifferentId) {
}
/**
+ * Stylus pointer coordinates are not resampled, but an event is still generated for the batch with
+ * a resampled timestamp and should be marked as such.
+ */
+TEST_F(TouchResamplingTest, StylusCoordinatesNotResampledFor) {
+ std::chrono::nanoseconds frameTime;
+ std::vector<InputEventEntry> entries, expectedEntries;
+
+ // Initial ACTION_DOWN should be separate, because the first consume event will only return
+ // InputEvent with a single action.
+ entries = {
+ // id x y
+ {0ms, {{0, 10, 20, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_DOWN},
+ };
+ publishInputEventEntries(entries);
+ frameTime = 5ms;
+ expectedEntries = {
+ // id x y
+ {0ms, {{0, 10, 20, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_DOWN},
+ };
+ consumeInputEventEntries(expectedEntries, frameTime);
+
+ // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+ entries = {
+ // id x y
+ {10ms, {{0, 20, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
+ {20ms, {{0, 30, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
+ };
+ publishInputEventEntries(entries);
+ frameTime = 35ms;
+ expectedEntries = {
+ // id x y
+ {10ms, {{0, 20, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
+ {20ms, {{0, 30, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
+ // A resampled event is generated, but the stylus coordinates are not resampled.
+ {25ms,
+ {{0, 30, 30, .toolType = ToolType::STYLUS, .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE},
+ };
+ consumeInputEventEntries(expectedEntries, frameTime);
+}
+
+/**
* Event should not be resampled when sample time is equal to event time.
*/
TEST_F(TouchResamplingTest, SampleTimeEqualsEventTime) {
@@ -544,13 +591,13 @@ TEST_F(TouchResamplingTest, TwoPointersAreResampledIndependently) {
// First pointer id=0 leaves the screen
entries = {
// id x y
- {80ms, {{1, 600, 600}}, actionPointer0Up},
+ {80ms, {{0, 120, 120}, {1, 600, 600}}, actionPointer0Up},
};
publishInputEventEntries(entries);
frameTime = 90ms;
expectedEntries = {
// id x y
- {80ms, {{1, 600, 600}}, actionPointer0Up},
+ {80ms, {{0, 120, 120}, {1, 600, 600}}, actionPointer0Up},
// no resampled event for ACTION_POINTER_UP
};
consumeInputEventEntries(expectedEntries, frameTime);
diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp
index 73f25cc615..f9ca28083d 100644
--- a/libs/input/tests/VelocityTracker_test.cpp
+++ b/libs/input/tests/VelocityTracker_test.cpp
@@ -42,8 +42,8 @@ constexpr int32_t DEFAULT_POINTER_ID = 0; // pointer ID used for manually define
// here EV = expected value, tol = VELOCITY_TOLERANCE
constexpr float VELOCITY_TOLERANCE = 0.2;
-// estimate coefficients must be within 0.001% of the target value
-constexpr float COEFFICIENT_TOLERANCE = 0.00001;
+// quadratic velocity must be within 0.001% of the target value
+constexpr float QUADRATIC_VELOCITY_TOLERANCE = 0.00001;
// --- VelocityTrackerTest ---
class VelocityTrackerTest : public testing::Test { };
@@ -76,10 +76,6 @@ static void checkVelocity(std::optional<float> Vactual, std::optional<float> Vta
}
}
-static void checkCoefficient(float actual, float target) {
- EXPECT_NEAR_BY_FRACTION(actual, target, COEFFICIENT_TOLERANCE);
-}
-
struct Position {
float x;
float y;
@@ -233,41 +229,23 @@ static std::vector<MotionEvent> createTouchMotionEventStream(
return events;
}
-static std::optional<float> computePlanarVelocity(
- const VelocityTracker::Strategy strategy,
- const std::vector<PlanarMotionEventEntry>& motions, int32_t axis,
- uint32_t pointerId = DEFAULT_POINTER_ID) {
+static std::optional<float> computeVelocity(const VelocityTracker::Strategy strategy,
+ const std::vector<MotionEvent>& events, int32_t axis,
+ uint32_t pointerId = DEFAULT_POINTER_ID) {
VelocityTracker vt(strategy);
- std::vector<MotionEvent> events = createTouchMotionEventStream(motions);
- for (MotionEvent event : events) {
- vt.addMovement(&event);
+ for (const MotionEvent& event : events) {
+ vt.addMovement(event);
}
return vt.getVelocity(axis, pointerId);
}
-static std::vector<MotionEvent> createMotionEventStream(
- int32_t axis, const std::vector<std::pair<std::chrono::nanoseconds, float>>& motion) {
- switch (axis) {
- case AMOTION_EVENT_AXIS_SCROLL:
- return createAxisScrollMotionEventStream(motion);
- default:
- ADD_FAILURE() << "Axis " << axis << " is not supported";
- return {};
- }
-}
-
-static std::optional<float> computeVelocity(
+static std::optional<float> computePlanarVelocity(
const VelocityTracker::Strategy strategy,
- const std::vector<std::pair<std::chrono::nanoseconds, float>>& motions, int32_t axis) {
- VelocityTracker vt(strategy);
-
- for (const MotionEvent& event : createMotionEventStream(axis, motions)) {
- vt.addMovement(&event);
- }
-
- return vt.getVelocity(axis, DEFAULT_POINTER_ID);
+ const std::vector<PlanarMotionEventEntry>& motions, int32_t axis, uint32_t pointerId) {
+ std::vector<MotionEvent> events = createTouchMotionEventStream(motions);
+ return computeVelocity(strategy, events, axis, pointerId);
}
static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy,
@@ -281,29 +259,28 @@ static void computeAndCheckAxisScrollVelocity(
const VelocityTracker::Strategy strategy,
const std::vector<std::pair<std::chrono::nanoseconds, float>>& motions,
std::optional<float> targetVelocity) {
- checkVelocity(computeVelocity(strategy, motions, AMOTION_EVENT_AXIS_SCROLL), targetVelocity);
+ std::vector<MotionEvent> events = createAxisScrollMotionEventStream(motions);
+ checkVelocity(computeVelocity(strategy, events, AMOTION_EVENT_AXIS_SCROLL), targetVelocity);
// The strategy LSQ2 is not compatible with AXIS_SCROLL. In those situations, we should fall
// back to a strategy that supports differential axes.
- checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, motions,
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, events,
AMOTION_EVENT_AXIS_SCROLL),
targetVelocity);
}
-static void computeAndCheckQuadraticEstimate(const std::vector<PlanarMotionEventEntry>& motions,
- const std::array<float, 3>& coefficients) {
- VelocityTracker vt(VelocityTracker::Strategy::LSQ2);
- std::vector<MotionEvent> events = createTouchMotionEventStream(motions);
- for (MotionEvent event : events) {
- vt.addMovement(&event);
- }
- std::optional<VelocityTracker::Estimator> estimatorX = vt.getEstimator(AMOTION_EVENT_AXIS_X, 0);
- std::optional<VelocityTracker::Estimator> estimatorY = vt.getEstimator(AMOTION_EVENT_AXIS_Y, 0);
- EXPECT_TRUE(estimatorX);
- EXPECT_TRUE(estimatorY);
- for (size_t i = 0; i< coefficients.size(); i++) {
- checkCoefficient((*estimatorX).coeff[i], coefficients[i]);
- checkCoefficient((*estimatorY).coeff[i], coefficients[i]);
- }
+static void computeAndCheckQuadraticVelocity(const std::vector<PlanarMotionEventEntry>& motions,
+ float velocity) {
+ std::optional<float> velocityX =
+ computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X,
+ DEFAULT_POINTER_ID);
+ std::optional<float> velocityY =
+ computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y,
+ DEFAULT_POINTER_ID);
+ ASSERT_TRUE(velocityX);
+ ASSERT_TRUE(velocityY);
+
+ EXPECT_NEAR_BY_FRACTION(*velocityX, velocity, QUADRATIC_VELOCITY_TOLERANCE);
+ EXPECT_NEAR_BY_FRACTION(*velocityY, velocity, QUADRATIC_VELOCITY_TOLERANCE);
}
/*
@@ -335,12 +312,14 @@ TEST_F(VelocityTrackerTest, TestDefaultStrategiesForPlanarAxes) {
{30ms, {{6, 20}}},
{40ms, {{10, 30}}}};
- EXPECT_EQ(computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X),
+ EXPECT_EQ(computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X,
+ DEFAULT_POINTER_ID),
computePlanarVelocity(VelocityTracker::Strategy::DEFAULT, motions,
- AMOTION_EVENT_AXIS_X));
- EXPECT_EQ(computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y),
+ AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID));
+ EXPECT_EQ(computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y,
+ DEFAULT_POINTER_ID),
computePlanarVelocity(VelocityTracker::Strategy::DEFAULT, motions,
- AMOTION_EVENT_AXIS_Y));
+ AMOTION_EVENT_AXIS_Y, DEFAULT_POINTER_ID));
}
TEST_F(VelocityTrackerTest, TestComputedVelocity) {
@@ -436,7 +415,7 @@ TEST_F(VelocityTrackerTest, TestGetComputedVelocity) {
VelocityTracker vt(VelocityTracker::Strategy::IMPULSE);
std::vector<MotionEvent> events = createTouchMotionEventStream(motions);
for (const MotionEvent& event : events) {
- vt.addMovement(&event);
+ vt.addMovement(event);
}
float maxFloat = std::numeric_limits<float>::max();
@@ -466,8 +445,6 @@ TEST_F(VelocityTrackerTest, TestApiInteractionsWithNoMotionEvents) {
EXPECT_FALSE(vt.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID));
- EXPECT_FALSE(vt.getEstimator(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID));
-
VelocityTracker::ComputedVelocity computedVelocity = vt.getComputedVelocity(1000, 1000);
for (uint32_t id = 0; id <= MAX_POINTER_ID; id++) {
EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, id));
@@ -516,6 +493,89 @@ TEST_F(VelocityTrackerTest, ThreePointsLinearVelocityTest) {
computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 500);
}
+/**
+ * When the stream is terminated with ACTION_CANCEL, the resulting velocity should be 0.
+ */
+TEST_F(VelocityTrackerTest, ActionCancelResultsInZeroVelocity) {
+ std::vector<PlanarMotionEventEntry> motions = {
+ {0ms, {{0, 0}}}, // DOWN
+ {10ms, {{5, 10}}}, // MOVE
+ {20ms, {{10, 20}}}, // MOVE
+ {20ms, {{10, 20}}}, // ACTION_UP
+ };
+ std::vector<MotionEvent> events = createTouchMotionEventStream(motions);
+ // By default, `createTouchMotionEventStream` produces an event stream that terminates with
+ // ACTION_UP. We need to manually change it to ACTION_CANCEL.
+ MotionEvent& lastEvent = events.back();
+ lastEvent.setAction(AMOTION_EVENT_ACTION_CANCEL);
+ lastEvent.setFlags(lastEvent.getFlags() | AMOTION_EVENT_FLAG_CANCELED);
+ const int32_t pointerId = lastEvent.getPointerId(0);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::IMPULSE, events, AMOTION_EVENT_AXIS_X,
+ pointerId),
+ /*targetVelocity*/ std::nullopt);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::IMPULSE, events, AMOTION_EVENT_AXIS_Y,
+ pointerId),
+ /*targetVelocity*/ std::nullopt);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, events, AMOTION_EVENT_AXIS_X,
+ pointerId),
+ /*targetVelocity*/ std::nullopt);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, events, AMOTION_EVENT_AXIS_Y,
+ pointerId),
+ /*targetVelocity*/ std::nullopt);
+}
+
+/**
+ * When the stream is terminated with ACTION_CANCEL, the resulting velocity should be 0.
+ */
+TEST_F(VelocityTrackerTest, ActionPointerCancelResultsInZeroVelocityForThatPointer) {
+ std::vector<PlanarMotionEventEntry> motions = {
+ {0ms, {{0, 5}, {NAN, NAN}}}, // DOWN
+ {0ms, {{0, 5}, {10, 15}}}, // POINTER_DOWN
+ {10ms, {{5, 10}, {15, 20}}}, // MOVE
+ {20ms, {{10, 15}, {20, 25}}}, // MOVE
+ {30ms, {{10, 15}, {20, 25}}}, // POINTER_UP
+ {30ms, {{10, 15}, {NAN, NAN}}}, // UP
+ };
+ std::vector<MotionEvent> events = createTouchMotionEventStream(motions);
+ // Cancel the lifting pointer of the ACTION_POINTER_UP event
+ MotionEvent& pointerUpEvent = events.rbegin()[1];
+ pointerUpEvent.setFlags(pointerUpEvent.getFlags() | AMOTION_EVENT_FLAG_CANCELED);
+ const int32_t pointerId = pointerUpEvent.getPointerId(pointerUpEvent.getActionIndex());
+ // Double check the stream
+ ASSERT_EQ(1, pointerId);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP, pointerUpEvent.getActionMasked());
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, events.back().getActionMasked());
+
+ // Ensure the velocity of the lifting pointer is zero
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::IMPULSE, events, AMOTION_EVENT_AXIS_X,
+ pointerId),
+ /*targetVelocity*/ std::nullopt);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::IMPULSE, events, AMOTION_EVENT_AXIS_Y,
+ pointerId),
+ /*targetVelocity*/ std::nullopt);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, events, AMOTION_EVENT_AXIS_X,
+ pointerId),
+ /*targetVelocity*/ std::nullopt);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, events, AMOTION_EVENT_AXIS_Y,
+ pointerId),
+ /*targetVelocity*/ std::nullopt);
+
+ // The remaining pointer should have the correct velocity.
+ const int32_t remainingPointerId = events.back().getPointerId(0);
+ ASSERT_EQ(0, remainingPointerId);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::IMPULSE, events, AMOTION_EVENT_AXIS_X,
+ remainingPointerId),
+ /*targetVelocity*/ 500);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::IMPULSE, events, AMOTION_EVENT_AXIS_Y,
+ remainingPointerId),
+ /*targetVelocity*/ 500);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, events, AMOTION_EVENT_AXIS_X,
+ remainingPointerId),
+ /*targetVelocity*/ 500);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, events, AMOTION_EVENT_AXIS_Y,
+ remainingPointerId),
+ /*targetVelocity*/ 500);
+}
/**
* ================== VelocityTracker tests generated by recording real events =====================
@@ -1079,7 +1139,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownFast3) {
* If the events with POINTER_UP or POINTER_DOWN are not handled correctly (these should not be
* part of the fitted data), this can cause large velocity values to be reported instead.
*/
-TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_ThreeFingerTap) {
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_ThreeFingerTap) {
std::vector<PlanarMotionEventEntry> motions = {
{ 0us, {{1063, 1128}, {NAN, NAN}, {NAN, NAN}} },
{ 10800us, {{1063, 1128}, {682, 1318}, {NAN, NAN}} }, // POINTER_DOWN
@@ -1167,7 +1227,7 @@ TEST_F(VelocityTrackerTest, LongDelayBeforeActionPointerUp) {
* ================== Tests for least squares fitting ==============================================
*
* Special care must be taken when constructing tests for LeastSquaresVelocityTrackerStrategy
- * getEstimator function. In particular:
+ * getVelocity function. In particular:
* - inside the function, time gets converted from nanoseconds to seconds
* before being used in the fit.
* - any values that are older than 100 ms are being discarded.
@@ -1188,7 +1248,7 @@ TEST_F(VelocityTrackerTest, LongDelayBeforeActionPointerUp) {
* The coefficients are (0, 0, 1).
* In the test, we would convert these coefficients to (0*(1E3)^0, 0*(1E3)^1, 1*(1E3)^2).
*/
-TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Constant) {
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Constant) {
std::vector<PlanarMotionEventEntry> motions = {
{ 0ms, {{1, 1}} }, // 0 s
{ 1ms, {{1, 1}} }, // 0.001 s
@@ -1200,13 +1260,13 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Constan
// -0.002, 1
// -0.001, 1
// -0.ms, 1
- computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({1, 0, 0}));
+ computeAndCheckQuadraticVelocity(motions, 0);
}
/*
* Straight line y = x :: the constant and quadratic coefficients are zero.
*/
-TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Linear) {
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Linear) {
std::vector<PlanarMotionEventEntry> motions = {
{ 0ms, {{-2, -2}} },
{ 1ms, {{-1, -1}} },
@@ -1218,13 +1278,13 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Linear)
// -0.002, -2
// -0.001, -1
// -0.000, 0
- computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({0, 1E3, 0}));
+ computeAndCheckQuadraticVelocity(motions, 1E3);
}
/*
* Parabola
*/
-TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic) {
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Parabolic) {
std::vector<PlanarMotionEventEntry> motions = {
{ 0ms, {{1, 1}} },
{ 1ms, {{4, 4}} },
@@ -1236,13 +1296,13 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabol
// -0.002, 1
// -0.001, 4
// -0.000, 8
- computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({8, 4.5E3, 0.5E6}));
+ computeAndCheckQuadraticVelocity(motions, 4.5E3);
}
/*
* Parabola
*/
-TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic2) {
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Parabolic2) {
std::vector<PlanarMotionEventEntry> motions = {
{ 0ms, {{1, 1}} },
{ 1ms, {{4, 4}} },
@@ -1254,13 +1314,13 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabol
// -0.002, 1
// -0.001, 4
// -0.000, 9
- computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({9, 6E3, 1E6}));
+ computeAndCheckQuadraticVelocity(motions, 6E3);
}
/*
* Parabola :: y = x^2 :: the constant and linear coefficients are zero.
*/
-TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic3) {
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Parabolic3) {
std::vector<PlanarMotionEventEntry> motions = {
{ 0ms, {{4, 4}} },
{ 1ms, {{1, 1}} },
@@ -1272,7 +1332,7 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabol
// -0.002, 4
// -0.001, 1
// -0.000, 0
- computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({0, 0E3, 1E6}));
+ computeAndCheckQuadraticVelocity(motions, 0E3);
}
// Recorded by hand on sailfish, but only the diffs are taken to test cumulative axis velocity.
@@ -1343,9 +1403,10 @@ TEST_F(VelocityTrackerTest, TestDefaultStrategyForAxisScroll) {
{40ms, 100},
};
- EXPECT_EQ(computeVelocity(VelocityTracker::Strategy::IMPULSE, motions,
+ std::vector<MotionEvent> events = createAxisScrollMotionEventStream(motions);
+ EXPECT_EQ(computeVelocity(VelocityTracker::Strategy::IMPULSE, events,
AMOTION_EVENT_AXIS_SCROLL),
- computeVelocity(VelocityTracker::Strategy::DEFAULT, motions,
+ computeVelocity(VelocityTracker::Strategy::DEFAULT, events,
AMOTION_EVENT_AXIS_SCROLL));
}
diff --git a/libs/nativedisplay/ADisplay.cpp b/libs/nativedisplay/ADisplay.cpp
index bf0805b46c..e3be3bc8f8 100644
--- a/libs/nativedisplay/ADisplay.cpp
+++ b/libs/nativedisplay/ADisplay.cpp
@@ -155,7 +155,7 @@ int ADisplay_acquirePhysicalDisplays(ADisplay*** outDisplays) {
const ui::DisplayMode& mode = modes[j];
modesPerDisplay[i].emplace_back(
DisplayConfigImpl{static_cast<size_t>(mode.id), mode.resolution.getWidth(),
- mode.resolution.getHeight(), mode.refreshRate,
+ mode.resolution.getHeight(), mode.peakRefreshRate,
mode.sfVsyncOffset, mode.appVsyncOffset});
}
}
diff --git a/libs/nativedisplay/Android.bp b/libs/nativedisplay/Android.bp
index 8d8a2bc244..342f5de337 100644
--- a/libs/nativedisplay/Android.bp
+++ b/libs/nativedisplay/Android.bp
@@ -73,6 +73,8 @@ cc_library_shared {
"libGLESv2",
],
+ static_libs: ["libguiflags"],
+
export_header_lib_headers: ["jni_headers"],
header_libs: [
diff --git a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
index 0f119f3fc1..099f47dbe1 100644
--- a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
+++ b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
@@ -19,6 +19,7 @@
#include <android/hardware_buffer.h>
#include <gui/BufferQueueDefs.h>
#include <gui/ConsumerBase.h>
+
#include <gui/IGraphicBufferProducer.h>
#include <sys/cdefs.h>
#include <system/graphics.h>
@@ -290,6 +291,20 @@ public:
*/
void releaseConsumerOwnership();
+ /**
+ * Interface for SurfaceTexture callback(s).
+ */
+ struct SurfaceTextureListener : public RefBase {
+ virtual void onFrameAvailable(const BufferItem& item) = 0;
+ virtual void onSetFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) = 0;
+ };
+
+ /**
+ * setSurfaceTextureListener registers a SurfaceTextureListener.
+ */
+ void setSurfaceTextureListener(const sp<SurfaceTextureListener>&);
+
protected:
/**
* abandonLocked overrides the ConsumerBase method to clear
@@ -335,6 +350,14 @@ protected:
void computeCurrentTransformMatrixLocked();
/**
+ * onSetFrameRate Notifies the consumer of a setFrameRate call from the producer side.
+ */
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+ void onSetFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) override;
+#endif
+
+ /**
* The default consumer usage flags that SurfaceTexture always sets on its
* BufferQueue instance; these will be OR:d with any additional flags passed
* from the SurfaceTexture user. In particular, SurfaceTexture will always
@@ -465,8 +488,30 @@ protected:
*/
ImageConsumer mImageConsumer;
+ /**
+ * mSurfaceTextureListener holds the registered SurfaceTextureListener.
+ * Note that SurfaceTexture holds the lister with an sp<>, which means that the listener
+ * must only hold a wp<> to SurfaceTexture and not an sp<>.
+ */
+ sp<SurfaceTextureListener> mSurfaceTextureListener;
+
friend class ImageConsumer;
friend class EGLConsumer;
+
+private:
+ // Proxy listener to avoid having SurfaceTexture directly implement FrameAvailableListener as it
+ // is extending ConsumerBase which also implements FrameAvailableListener.
+ class FrameAvailableListenerProxy : public ConsumerBase::FrameAvailableListener {
+ public:
+ FrameAvailableListenerProxy(const wp<SurfaceTextureListener>& listener)
+ : mSurfaceTextureListener(listener) {}
+
+ private:
+ void onFrameAvailable(const BufferItem& item) override;
+
+ const wp<SurfaceTextureListener> mSurfaceTextureListener;
+ };
+ sp<FrameAvailableListenerProxy> mFrameAvailableListenerProxy;
};
// ----------------------------------------------------------------------------
diff --git a/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp b/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
index 9f610e1a50..3a09204878 100644
--- a/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
+++ b/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
@@ -23,6 +23,8 @@
#include <system/window.h>
#include <utils/Trace.h>
+#include <com_android_graphics_libgui_flags.h>
+
namespace android {
// Macros for including the SurfaceTexture name in log messages
@@ -491,4 +493,42 @@ sp<GraphicBuffer> SurfaceTexture::dequeueBuffer(int* outSlotid, android_dataspac
return buffer;
}
+void SurfaceTexture::setSurfaceTextureListener(
+ const sp<android::SurfaceTexture::SurfaceTextureListener>& listener) {
+ SFT_LOGV("setSurfaceTextureListener");
+
+ Mutex::Autolock _l(mMutex);
+ mSurfaceTextureListener = listener;
+ if (mSurfaceTextureListener != nullptr) {
+ mFrameAvailableListenerProxy =
+ sp<FrameAvailableListenerProxy>::make(mSurfaceTextureListener);
+ setFrameAvailableListener(mFrameAvailableListenerProxy);
+ } else {
+ mFrameAvailableListenerProxy.clear();
+ }
+}
+
+void SurfaceTexture::FrameAvailableListenerProxy::onFrameAvailable(const BufferItem& item) {
+ const auto listener = mSurfaceTextureListener.promote();
+ if (listener) {
+ listener->onFrameAvailable(item);
+ }
+}
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+void SurfaceTexture::onSetFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) {
+ SFT_LOGV("onSetFrameRate: %.2f", frameRate);
+
+ auto listener = [&] {
+ Mutex::Autolock _l(mMutex);
+ return mSurfaceTextureListener;
+ }();
+
+ if (listener) {
+ listener->onSetFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+ }
+}
+#endif
+
} // namespace android
diff --git a/libs/nativewindow/AHardwareBuffer.cpp b/libs/nativewindow/AHardwareBuffer.cpp
index 80607055ed..e7b2195056 100644
--- a/libs/nativewindow/AHardwareBuffer.cpp
+++ b/libs/nativewindow/AHardwareBuffer.cpp
@@ -40,6 +40,80 @@ static constexpr int kFdBufferSize = 128 * sizeof(int); // 128 ints
using namespace android;
// ----------------------------------------------------------------------------
+// Validate hardware_buffer.h and PixelFormat.aidl agree
+// ----------------------------------------------------------------------------
+
+static_assert(HAL_PIXEL_FORMAT_RGBA_8888 == AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RGBX_8888 == AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RGB_565 == AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RGB_888 == AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RGBA_FP16 == AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RGBA_1010102 == AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_BLOB == AHARDWAREBUFFER_FORMAT_BLOB,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_DEPTH_16 == AHARDWAREBUFFER_FORMAT_D16_UNORM,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_DEPTH_24 == AHARDWAREBUFFER_FORMAT_D24_UNORM,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_DEPTH_24_STENCIL_8 == AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_DEPTH_32F == AHARDWAREBUFFER_FORMAT_D32_FLOAT,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_DEPTH_32F_STENCIL_8 == AHARDWAREBUFFER_FORMAT_D32_FLOAT_S8_UINT,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_STENCIL_8 == AHARDWAREBUFFER_FORMAT_S8_UINT,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_BGRA_8888 == AHARDWAREBUFFER_FORMAT_B8G8R8A8_UNORM,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_YV12 == AHARDWAREBUFFER_FORMAT_YV12,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_Y8 == AHARDWAREBUFFER_FORMAT_Y8,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_Y16 == AHARDWAREBUFFER_FORMAT_Y16,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RAW16 == AHARDWAREBUFFER_FORMAT_RAW16,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RAW10 == AHARDWAREBUFFER_FORMAT_RAW10,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RAW12 == AHARDWAREBUFFER_FORMAT_RAW12,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RAW_OPAQUE == AHARDWAREBUFFER_FORMAT_RAW_OPAQUE,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED ==
+ AHARDWAREBUFFER_FORMAT_IMPLEMENTATION_DEFINED,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_YCBCR_420_888 == AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_YCBCR_422_SP == AHARDWAREBUFFER_FORMAT_YCbCr_422_SP,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_YCRCB_420_SP == AHARDWAREBUFFER_FORMAT_YCrCb_420_SP,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_YCBCR_422_I == AHARDWAREBUFFER_FORMAT_YCbCr_422_I,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_YCBCR_P010 == AHARDWAREBUFFER_FORMAT_YCbCr_P010,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::R_8) ==
+ AHARDWAREBUFFER_FORMAT_R8_UNORM,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::R_16_UINT) ==
+ AHARDWAREBUFFER_FORMAT_R16_UINT,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(
+ static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::RG_1616_UINT) ==
+ AHARDWAREBUFFER_FORMAT_R16G16_UINT,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(
+ static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::RGBA_10101010) ==
+ AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM,
+ "HAL and AHardwareBuffer pixel format don't match");
+
+// ----------------------------------------------------------------------------
// Public functions
// ----------------------------------------------------------------------------
@@ -227,11 +301,14 @@ int AHardwareBuffer_lockPlanes(AHardwareBuffer* buffer, uint64_t usage,
}
return result;
} else {
- const uint32_t pixelStride = AHardwareBuffer_bytesPerPixel(format);
+ int32_t bytesPerPixel;
+ int32_t bytesPerStride;
+ int result = gBuffer->lockAsync(usage, usage, bounds, &outPlanes->planes[0].data, fence,
+ &bytesPerPixel, &bytesPerStride);
outPlanes->planeCount = 1;
- outPlanes->planes[0].pixelStride = pixelStride;
- outPlanes->planes[0].rowStride = gBuffer->getStride() * pixelStride;
- return gBuffer->lockAsync(usage, usage, bounds, &outPlanes->planes[0].data, fence);
+ outPlanes->planes[0].pixelStride = bytesPerPixel;
+ outPlanes->planes[0].rowStride = bytesPerStride;
+ return result;
}
}
@@ -487,12 +564,6 @@ bool AHardwareBuffer_isValidDescription(const AHardwareBuffer_Desc* desc, bool l
return false;
}
- if (!AHardwareBuffer_isValidPixelFormat(desc->format)) {
- ALOGE_IF(log, "Invalid AHardwareBuffer pixel format %u (%#x))",
- desc->format, desc->format);
- return false;
- }
-
if (desc->rfu0 != 0 || desc->rfu1 != 0) {
ALOGE_IF(log, "AHardwareBuffer_Desc::rfu fields must be 0");
return false;
@@ -557,114 +628,6 @@ bool AHardwareBuffer_isValidDescription(const AHardwareBuffer_Desc* desc, bool l
return true;
}
-bool AHardwareBuffer_isValidPixelFormat(uint32_t format) {
- static_assert(HAL_PIXEL_FORMAT_RGBA_8888 == AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_RGBX_8888 == AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_RGB_565 == AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_RGB_888 == AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_RGBA_FP16 == AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_RGBA_1010102 == AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_BLOB == AHARDWAREBUFFER_FORMAT_BLOB,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_DEPTH_16 == AHARDWAREBUFFER_FORMAT_D16_UNORM,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_DEPTH_24 == AHARDWAREBUFFER_FORMAT_D24_UNORM,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_DEPTH_24_STENCIL_8 == AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_DEPTH_32F == AHARDWAREBUFFER_FORMAT_D32_FLOAT,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_DEPTH_32F_STENCIL_8 == AHARDWAREBUFFER_FORMAT_D32_FLOAT_S8_UINT,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_STENCIL_8 == AHARDWAREBUFFER_FORMAT_S8_UINT,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_BGRA_8888 == AHARDWAREBUFFER_FORMAT_B8G8R8A8_UNORM,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_YV12 == AHARDWAREBUFFER_FORMAT_YV12,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_Y8 == AHARDWAREBUFFER_FORMAT_Y8,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_Y16 == AHARDWAREBUFFER_FORMAT_Y16,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_RAW16 == AHARDWAREBUFFER_FORMAT_RAW16,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_RAW10 == AHARDWAREBUFFER_FORMAT_RAW10,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_RAW12 == AHARDWAREBUFFER_FORMAT_RAW12,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_RAW_OPAQUE == AHARDWAREBUFFER_FORMAT_RAW_OPAQUE,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED == AHARDWAREBUFFER_FORMAT_IMPLEMENTATION_DEFINED,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_YCBCR_420_888 == AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_YCBCR_422_SP == AHARDWAREBUFFER_FORMAT_YCbCr_422_SP,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_YCRCB_420_SP == AHARDWAREBUFFER_FORMAT_YCrCb_420_SP,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_YCBCR_422_I == AHARDWAREBUFFER_FORMAT_YCbCr_422_I,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_YCBCR_P010 == AHARDWAREBUFFER_FORMAT_YCbCr_P010,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::R_8) ==
- AHARDWAREBUFFER_FORMAT_R8_UNORM,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::R_16_UINT) ==
- AHARDWAREBUFFER_FORMAT_R16_UINT,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::RG_1616_UINT) ==
- AHARDWAREBUFFER_FORMAT_R16G16_UINT,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::RGBA_10101010) ==
- AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM,
- "HAL and AHardwareBuffer pixel format don't match");
-
- switch (format) {
- case AHARDWAREBUFFER_FORMAT_R8_UNORM:
- case AHARDWAREBUFFER_FORMAT_R16_UINT:
- case AHARDWAREBUFFER_FORMAT_R16G16_UINT:
- case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
- case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
- case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
- case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
- case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
- case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
- case AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM:
- case AHARDWAREBUFFER_FORMAT_BLOB:
- case AHARDWAREBUFFER_FORMAT_D16_UNORM:
- case AHARDWAREBUFFER_FORMAT_D24_UNORM:
- case AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT:
- case AHARDWAREBUFFER_FORMAT_D32_FLOAT:
- case AHARDWAREBUFFER_FORMAT_D32_FLOAT_S8_UINT:
- case AHARDWAREBUFFER_FORMAT_S8_UINT:
- case AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420:
- // VNDK formats only -- unfortunately we can't differentiate from where we're called
- case AHARDWAREBUFFER_FORMAT_B8G8R8A8_UNORM:
- case AHARDWAREBUFFER_FORMAT_YV12:
- case AHARDWAREBUFFER_FORMAT_Y8:
- case AHARDWAREBUFFER_FORMAT_Y16:
- case AHARDWAREBUFFER_FORMAT_RAW16:
- case AHARDWAREBUFFER_FORMAT_RAW10:
- case AHARDWAREBUFFER_FORMAT_RAW12:
- case AHARDWAREBUFFER_FORMAT_RAW_OPAQUE:
- case AHARDWAREBUFFER_FORMAT_IMPLEMENTATION_DEFINED:
- case AHARDWAREBUFFER_FORMAT_YCbCr_422_SP:
- case AHARDWAREBUFFER_FORMAT_YCrCb_420_SP:
- case AHARDWAREBUFFER_FORMAT_YCbCr_422_I:
- case AHARDWAREBUFFER_FORMAT_YCbCr_P010:
- return true;
-
- default:
- return false;
- }
-}
-
bool AHardwareBuffer_formatIsYuv(uint32_t format) {
switch (format) {
case AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420:
@@ -681,32 +644,6 @@ bool AHardwareBuffer_formatIsYuv(uint32_t format) {
}
}
-uint32_t AHardwareBuffer_bytesPerPixel(uint32_t format) {
- switch (format) {
- case AHARDWAREBUFFER_FORMAT_R8_UNORM:
- return 1;
- case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
- case AHARDWAREBUFFER_FORMAT_D16_UNORM:
- case AHARDWAREBUFFER_FORMAT_R16_UINT:
- return 2;
- case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
- case AHARDWAREBUFFER_FORMAT_D24_UNORM:
- return 3;
- case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
- case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
- case AHARDWAREBUFFER_FORMAT_D32_FLOAT:
- case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
- case AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT:
- case AHARDWAREBUFFER_FORMAT_R16G16_UINT:
- return 4;
- case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
- case AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM:
- return 8;
- default:
- return 0;
- }
-}
-
uint32_t AHardwareBuffer_convertFromPixelFormat(uint32_t hal_format) {
return hal_format;
}
diff --git a/libs/nativewindow/TEST_MAPPING b/libs/nativewindow/TEST_MAPPING
index bbad757a48..9d6425bfe0 100644
--- a/libs/nativewindow/TEST_MAPPING
+++ b/libs/nativewindow/TEST_MAPPING
@@ -1,14 +1,12 @@
{
"presubmit": [
{
- "name": "libnativewindow_test"
- }
- ],
- "postsubmit": [
- {
"name": "libnativewindow_bindgen_test"
},
{
+ "name": "libnativewindow_test"
+ },
+ {
"name": "libnativewindow_rs-internal_test"
}
]
diff --git a/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h b/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h
index 6d3d295a0c..880c694934 100644
--- a/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h
+++ b/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h
@@ -37,15 +37,9 @@ namespace android {
// parameters. Note: this does not verify any platform-specific contraints.
bool AHardwareBuffer_isValidDescription(const AHardwareBuffer_Desc* desc, bool log);
-// whether this AHardwareBuffer format is valid
-bool AHardwareBuffer_isValidPixelFormat(uint32_t ahardwarebuffer_format);
-
// whether this is a YUV type format
bool AHardwareBuffer_formatIsYuv(uint32_t format);
-// number of bytes per pixel or 0 if unknown or multi-planar
-uint32_t AHardwareBuffer_bytesPerPixel(uint32_t format);
-
// convert AHardwareBuffer format to HAL format (note: this is a no-op)
uint32_t AHardwareBuffer_convertFromPixelFormat(uint32_t format);
diff --git a/libs/nativewindow/include/android/native_window_aidl.h b/libs/nativewindow/include/android/native_window_aidl.h
index ef3d2803f5..e496c453f2 100644
--- a/libs/nativewindow/include/android/native_window_aidl.h
+++ b/libs/nativewindow/include/android/native_window_aidl.h
@@ -34,6 +34,12 @@
#include <android/native_window.h>
#include <sys/cdefs.h>
+// Only required by the AIDL glue helper
+#ifdef __cplusplus
+#include <sstream>
+#include <string>
+#endif // __cplusplus
+
__BEGIN_DECLS
/**
@@ -80,7 +86,7 @@ namespace aidl::android::hardware {
* Takes ownership of the ANativeWindow* given to it in reset() and will automatically
* destroy it in the destructor, similar to a smart pointer container
*/
-class NativeWindow {
+class NativeWindow final {
public:
NativeWindow() noexcept {}
explicit NativeWindow(ANativeWindow* _Nullable window) {
@@ -131,15 +137,29 @@ public:
}
mWindow = window;
}
- inline ANativeWindow* _Nullable operator-> () const { return mWindow; }
+
inline ANativeWindow* _Nullable get() const { return mWindow; }
- inline explicit operator bool () const { return mWindow != nullptr; }
NativeWindow& operator=(NativeWindow&& other) noexcept {
mWindow = other.release(); // steal ownership from r-value
return *this;
}
+ inline ANativeWindow* _Nullable operator->() const { return mWindow; }
+ inline explicit operator bool() const { return mWindow != nullptr; }
+ inline bool operator==(const NativeWindow& rhs) const { return mWindow == rhs.mWindow; }
+ inline bool operator!=(const NativeWindow& rhs) const { return !(*this == rhs); }
+ inline bool operator<(const NativeWindow& rhs) const { return mWindow < rhs.mWindow; }
+ inline bool operator>(const NativeWindow& rhs) const { return rhs < *this; }
+ inline bool operator>=(const NativeWindow& rhs) const { return !(*this < rhs); }
+ inline bool operator<=(const NativeWindow& rhs) const { return !(*this > rhs); }
+
+ std::string toString() const {
+ std::ostringstream ss;
+ ss << "NativeWindow: " << mWindow;
+ return ss.str();
+ }
+
/**
* Stops managing any contained ANativeWindow*, returning it to the caller. Ownership
* is released.
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index edaa422e55..a98ea86073 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -1057,7 +1057,85 @@ enum {
/**
* This surface will vote for the minimum refresh rate.
*/
- ANATIVEWINDOW_FRAME_RATE_MIN
+ ANATIVEWINDOW_FRAME_RATE_MIN,
+
+ /**
+ * The surface requests a frame rate that is greater than or equal to `frameRate`.
+ */
+ ANATIVEWINDOW_FRAME_RATE_GTE
+};
+
+/*
+ * Frame rate category values that can be used in Transaction::setFrameRateCategory.
+ */
+enum {
+ /**
+ * Default value. This value can also be set to return to default behavior, such as layers
+ * without animations.
+ */
+ ANATIVEWINDOW_FRAME_RATE_CATEGORY_DEFAULT = 0,
+
+ /**
+ * The layer will explicitly not influence the frame rate.
+ * This may indicate a frame rate suitable for no animation updates (such as a cursor blinking
+ * or a sporadic update).
+ */
+ ANATIVEWINDOW_FRAME_RATE_CATEGORY_NO_PREFERENCE = 1,
+
+ /**
+ * Indicates a frame rate suitable for animations that looks fine even if played at a low frame
+ * rate.
+ */
+ ANATIVEWINDOW_FRAME_RATE_CATEGORY_LOW = 2,
+
+ /**
+ * Indicates a middle frame rate suitable for animations that do not require higher frame
+ * rates, or do not benefit from high smoothness. This is normally 60 Hz or close to it.
+ */
+ ANATIVEWINDOW_FRAME_RATE_CATEGORY_NORMAL = 3,
+
+ /**
+ * Indicates a frame rate suitable for animations that require a high frame rate, which may
+ * increase smoothness but may also increase power usage.
+ */
+ ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH = 4
+};
+
+/*
+ * Frame rate selection strategy values that can be used in
+ * Transaction::setFrameRateSelectionStrategy.
+ */
+enum {
+ /**
+ * Default value. The layer uses its own frame rate specifications, assuming it has any
+ * specifications, instead of its parent's. If it does not have its own frame rate
+ * specifications, it will try to use its parent's. It will propagate its specifications to any
+ * descendants that do not have their own.
+ *
+ * However, FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN on an ancestor layer
+ * supersedes this behavior, meaning that this layer will inherit frame rate specifications
+ * regardless of whether it has its own.
+ */
+ ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_PROPAGATE = 0,
+
+ /**
+ * The layer's frame rate specifications will propagate to and override those of its descendant
+ * layers.
+ *
+ * The layer itself has the FRAME_RATE_SELECTION_STRATEGY_PROPAGATE behavior.
+ * Thus, ancestor layer that also has the strategy
+ * FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN will override this layer's
+ * frame rate specifications.
+ */
+ ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN = 1,
+
+ /**
+ * The layer's frame rate specifications will not propagate to its descendant
+ * layers, even if the descendant layer has no frame rate specifications.
+ * However, FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN on an ancestor
+ * layer supersedes this behavior.
+ */
+ ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF = 2,
};
static inline int native_window_set_frame_rate(struct ANativeWindow* window, float frameRate,
diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt
index c6cd95307c..95fc920da1 100644
--- a/libs/nativewindow/libnativewindow.map.txt
+++ b/libs/nativewindow/libnativewindow.map.txt
@@ -65,7 +65,6 @@ LIBNATIVEWINDOW {
LIBNATIVEWINDOW_PLATFORM {
global:
extern "C++" {
- android::AHardwareBuffer_isValidPixelFormat*;
android::AHardwareBuffer_convertFromPixelFormat*;
android::AHardwareBuffer_convertToPixelFormat*;
android::AHardwareBuffer_convertFromGrallocUsageBits*;
diff --git a/libs/permission/aidl/android/content/AttributionSourceState.aidl b/libs/permission/aidl/android/content/AttributionSourceState.aidl
index ed1b37dc0b..b3fb7a7719 100644
--- a/libs/permission/aidl/android/content/AttributionSourceState.aidl
+++ b/libs/permission/aidl/android/content/AttributionSourceState.aidl
@@ -27,6 +27,10 @@ parcelable AttributionSourceState {
int pid = -1;
/** The UID that is accessing the permission protected data. */
int uid = -1;
+ /** The default device ID from where the permission protected data is read.
+ * @see Context#DEVICE_ID_DEFAULT
+ */
+ int deviceId = 0;
/** The package that is accessing the permission protected data. */
@nullable @utf8InCpp String packageName;
/** The attribution tag of the app accessing the permission protected data. */
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index 8d19c45527..ba2eb7d224 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -56,30 +56,8 @@ cc_defaults {
filegroup {
name: "librenderengine_sources",
srcs: [
- "Description.cpp",
"ExternalTexture.cpp",
- "Mesh.cpp",
"RenderEngine.cpp",
- "Texture.cpp",
- ],
-}
-
-filegroup {
- name: "librenderengine_gl_sources",
- srcs: [
- "gl/GLESRenderEngine.cpp",
- "gl/GLExtensions.cpp",
- "gl/GLFramebuffer.cpp",
- "gl/GLImage.cpp",
- "gl/GLShadowTexture.cpp",
- "gl/GLShadowVertexGenerator.cpp",
- "gl/GLSkiaShadowPort.cpp",
- "gl/GLVertexBuffer.cpp",
- "gl/ImageManager.cpp",
- "gl/Program.cpp",
- "gl/ProgramCache.cpp",
- "gl/filters/BlurFilter.cpp",
- "gl/filters/GenericProgram.cpp",
],
}
@@ -96,6 +74,7 @@ filegroup {
"skia/AutoBackendTexture.cpp",
"skia/Cache.cpp",
"skia/ColorSpaces.cpp",
+ "skia/GLExtensions.cpp",
"skia/SkiaRenderEngine.cpp",
"skia/SkiaGLRenderEngine.cpp",
"skia/SkiaVkRenderEngine.cpp",
@@ -136,7 +115,6 @@ cc_library_static {
],
srcs: [
":librenderengine_sources",
- ":librenderengine_gl_sources",
":librenderengine_threaded_sources",
":librenderengine_skia_sources",
],
@@ -155,8 +133,6 @@ cc_library_static {
name: "librenderengine_mocks",
defaults: ["librenderengine_defaults"],
srcs: [
- "mock/Framebuffer.cpp",
- "mock/Image.cpp",
"mock/RenderEngine.cpp",
],
static_libs: [
diff --git a/libs/renderengine/Description.cpp b/libs/renderengine/Description.cpp
deleted file mode 100644
index 245c9e1e30..0000000000
--- a/libs/renderengine/Description.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2013 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.
- */
-
-#include <renderengine/private/Description.h>
-
-#include <stdint.h>
-
-#include <utils/TypeHelpers.h>
-
-namespace android {
-namespace renderengine {
-
-Description::TransferFunction Description::dataSpaceToTransferFunction(ui::Dataspace dataSpace) {
- ui::Dataspace transfer = static_cast<ui::Dataspace>(dataSpace & ui::Dataspace::TRANSFER_MASK);
- switch (transfer) {
- case ui::Dataspace::TRANSFER_ST2084:
- return Description::TransferFunction::ST2084;
- case ui::Dataspace::TRANSFER_HLG:
- return Description::TransferFunction::HLG;
- case ui::Dataspace::TRANSFER_LINEAR:
- return Description::TransferFunction::LINEAR;
- default:
- return Description::TransferFunction::SRGB;
- }
-}
-
-bool Description::hasInputTransformMatrix() const {
- const mat4 identity;
- return inputTransformMatrix != identity;
-}
-
-bool Description::hasOutputTransformMatrix() const {
- const mat4 identity;
- return outputTransformMatrix != identity;
-}
-
-bool Description::hasColorMatrix() const {
- const mat4 identity;
- return colorMatrix != identity;
-}
-
-bool Description::hasDisplayColorMatrix() const {
- const mat4 identity;
- return displayColorMatrix != identity;
-}
-
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/ExternalTexture.cpp b/libs/renderengine/ExternalTexture.cpp
index 9eb42cd8e1..6f2a96a87b 100644
--- a/libs/renderengine/ExternalTexture.cpp
+++ b/libs/renderengine/ExternalTexture.cpp
@@ -27,14 +27,6 @@ ExternalTexture::ExternalTexture(const sp<GraphicBuffer>& buffer,
: mBuffer(buffer), mRenderEngine(renderEngine), mWritable(usage & WRITEABLE) {
LOG_ALWAYS_FATAL_IF(buffer == nullptr,
"Attempted to bind a null buffer to an external texture!");
- // GLESRenderEngine has a separate texture cache for output buffers,
- if (usage == WRITEABLE &&
- (mRenderEngine.getRenderEngineType() ==
- renderengine::RenderEngine::RenderEngineType::GLES ||
- mRenderEngine.getRenderEngineType() ==
- renderengine::RenderEngine::RenderEngineType::THREADED)) {
- return;
- }
mRenderEngine.mapExternalTextureBuffer(mBuffer, mWritable);
}
diff --git a/libs/renderengine/Mesh.cpp b/libs/renderengine/Mesh.cpp
deleted file mode 100644
index ed2f45fdf5..0000000000
--- a/libs/renderengine/Mesh.cpp
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright 2013 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.
- */
-
-#include <renderengine/Mesh.h>
-
-#include <utils/Log.h>
-
-namespace android {
-namespace renderengine {
-
-Mesh::Mesh(Primitive primitive, size_t vertexCount, size_t vertexSize, size_t texCoordSize,
- size_t cropCoordsSize, size_t shadowColorSize, size_t shadowParamsSize,
- size_t indexCount)
- : mVertexCount(vertexCount),
- mVertexSize(vertexSize),
- mTexCoordsSize(texCoordSize),
- mCropCoordsSize(cropCoordsSize),
- mShadowColorSize(shadowColorSize),
- mShadowParamsSize(shadowParamsSize),
- mPrimitive(primitive),
- mIndexCount(indexCount) {
- if (vertexCount == 0) {
- mVertices.resize(1);
- mVertices[0] = 0.0f;
- mStride = 0;
- return;
- }
- size_t stride = vertexSize + texCoordSize + cropCoordsSize + shadowColorSize + shadowParamsSize;
- size_t remainder = (stride * vertexCount) / vertexCount;
- // Since all of the input parameters are unsigned, if stride is less than
- // either vertexSize or texCoordSize, it must have overflowed. remainder
- // will be equal to stride as long as stride * vertexCount doesn't overflow.
- if ((stride < vertexSize) || (remainder != stride)) {
- ALOGE("Overflow in Mesh(..., %zu, %zu, %zu, %zu, %zu, %zu)", vertexCount, vertexSize,
- texCoordSize, cropCoordsSize, shadowColorSize, shadowParamsSize);
- mVertices.resize(1);
- mVertices[0] = 0.0f;
- mVertexCount = 0;
- mVertexSize = 0;
- mTexCoordsSize = 0;
- mCropCoordsSize = 0;
- mShadowColorSize = 0;
- mShadowParamsSize = 0;
- mStride = 0;
- return;
- }
-
- mVertices.resize(stride * vertexCount);
- mStride = stride;
- mIndices.resize(indexCount);
-}
-
-Mesh::Primitive Mesh::getPrimitive() const {
- return mPrimitive;
-}
-
-float const* Mesh::getPositions() const {
- return mVertices.data();
-}
-float* Mesh::getPositions() {
- return mVertices.data();
-}
-
-float const* Mesh::getTexCoords() const {
- return mVertices.data() + mVertexSize;
-}
-float* Mesh::getTexCoords() {
- return mVertices.data() + mVertexSize;
-}
-
-float const* Mesh::getCropCoords() const {
- return mVertices.data() + mVertexSize + mTexCoordsSize;
-}
-float* Mesh::getCropCoords() {
- return mVertices.data() + mVertexSize + mTexCoordsSize;
-}
-
-float const* Mesh::getShadowColor() const {
- return mVertices.data() + mVertexSize + mTexCoordsSize + mCropCoordsSize;
-}
-float* Mesh::getShadowColor() {
- return mVertices.data() + mVertexSize + mTexCoordsSize + mCropCoordsSize;
-}
-
-float const* Mesh::getShadowParams() const {
- return mVertices.data() + mVertexSize + mTexCoordsSize + mCropCoordsSize + mShadowColorSize;
-}
-float* Mesh::getShadowParams() {
- return mVertices.data() + mVertexSize + mTexCoordsSize + mCropCoordsSize + mShadowColorSize;
-}
-
-uint16_t const* Mesh::getIndices() const {
- return mIndices.data();
-}
-
-uint16_t* Mesh::getIndices() {
- return mIndices.data();
-}
-
-size_t Mesh::getVertexCount() const {
- return mVertexCount;
-}
-
-size_t Mesh::getVertexSize() const {
- return mVertexSize;
-}
-
-size_t Mesh::getTexCoordsSize() const {
- return mTexCoordsSize;
-}
-
-size_t Mesh::getShadowColorSize() const {
- return mShadowColorSize;
-}
-
-size_t Mesh::getShadowParamsSize() const {
- return mShadowParamsSize;
-}
-
-size_t Mesh::getByteStride() const {
- return mStride * sizeof(float);
-}
-
-size_t Mesh::getStride() const {
- return mStride;
-}
-
-size_t Mesh::getIndexCount() const {
- return mIndexCount;
-}
-
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp
index d08c2213ad..3e1ac33d57 100644
--- a/libs/renderengine/RenderEngine.cpp
+++ b/libs/renderengine/RenderEngine.cpp
@@ -18,7 +18,6 @@
#include <cutils/properties.h>
#include <log/log.h>
-#include "gl/GLESRenderEngine.h"
#include "renderengine/ExternalTexture.h"
#include "threaded/RenderEngineThreaded.h"
@@ -30,11 +29,6 @@ namespace renderengine {
std::unique_ptr<RenderEngine> RenderEngine::create(const RenderEngineCreationArgs& args) {
switch (args.renderEngineType) {
- case RenderEngineType::THREADED:
- ALOGD("Threaded RenderEngine with GLES Backend");
- return renderengine::threaded::RenderEngineThreaded::create(
- [args]() { return android::renderengine::gl::GLESRenderEngine::create(args); },
- args.renderEngineType);
case RenderEngineType::SKIA_GL:
ALOGD("RenderEngine with SkiaGL Backend");
return renderengine::skia::SkiaGLRenderEngine::create(args);
@@ -56,10 +50,6 @@ std::unique_ptr<RenderEngine> RenderEngine::create(const RenderEngineCreationArg
return android::renderengine::skia::SkiaVkRenderEngine::create(args);
},
args.renderEngineType);
- case RenderEngineType::GLES:
- default:
- ALOGD("RenderEngine with GLES Backend");
- return renderengine::gl::GLESRenderEngine::create(args);
}
}
@@ -78,13 +68,11 @@ void RenderEngine::validateOutputBufferUsage(const sp<GraphicBuffer>& buffer) {
ftl::Future<FenceResult> RenderEngine::drawLayers(const DisplaySettings& display,
const std::vector<LayerSettings>& layers,
const std::shared_ptr<ExternalTexture>& buffer,
- const bool useFramebufferCache,
base::unique_fd&& bufferFence) {
const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
std::future<FenceResult> resultFuture = resultPromise->get_future();
updateProtectedContext(layers, buffer);
- drawLayersInternal(std::move(resultPromise), display, layers, buffer, useFramebufferCache,
- std::move(bufferFence));
+ drawLayersInternal(std::move(resultPromise), display, layers, buffer, std::move(bufferFence));
return resultFuture;
}
diff --git a/libs/renderengine/Texture.cpp b/libs/renderengine/Texture.cpp
deleted file mode 100644
index 154cde80b9..0000000000
--- a/libs/renderengine/Texture.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2013 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.
- */
-
-#include <renderengine/Texture.h>
-
-namespace android {
-namespace renderengine {
-
-Texture::Texture()
- : mTextureName(0), mTextureTarget(TEXTURE_2D), mWidth(0), mHeight(0), mFiltering(false) {}
-
-Texture::Texture(Target textureTarget, uint32_t textureName)
- : mTextureName(textureName),
- mTextureTarget(textureTarget),
- mWidth(0),
- mHeight(0),
- mFiltering(false) {}
-
-void Texture::init(Target textureTarget, uint32_t textureName) {
- mTextureName = textureName;
- mTextureTarget = textureTarget;
-}
-
-Texture::~Texture() {}
-
-void Texture::setMatrix(float const* matrix) {
- mTextureMatrix = mat4(matrix);
-}
-
-void Texture::setFiltering(bool enabled) {
- mFiltering = enabled;
-}
-
-void Texture::setDimensions(size_t width, size_t height) {
- mWidth = width;
- mHeight = height;
-}
-
-uint32_t Texture::getTextureName() const {
- return mTextureName;
-}
-
-uint32_t Texture::getTextureTarget() const {
- return mTextureTarget;
-}
-
-const mat4& Texture::getMatrix() const {
- return mTextureMatrix;
-}
-
-bool Texture::getFiltering() const {
- return mFiltering;
-}
-
-size_t Texture::getWidth() const {
- return mWidth;
-}
-
-size_t Texture::getHeight() const {
- return mHeight;
-}
-
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/benchmark/RenderEngineBench.cpp b/libs/renderengine/benchmark/RenderEngineBench.cpp
index bd7b617ae7..a7f1df9a9b 100644
--- a/libs/renderengine/benchmark/RenderEngineBench.cpp
+++ b/libs/renderengine/benchmark/RenderEngineBench.cpp
@@ -43,10 +43,6 @@ std::string RenderEngineTypeName(RenderEngine::RenderEngineType type) {
return "skiavk";
case RenderEngine::RenderEngineType::SKIA_VK_THREADED:
return "skiavkthreaded";
- case RenderEngine::RenderEngineType::GLES:
- case RenderEngine::RenderEngineType::THREADED:
- LOG_ALWAYS_FATAL("GLESRenderEngine is deprecated - why time it?");
- return "unused";
}
}
@@ -108,10 +104,6 @@ std::pair<uint32_t, uint32_t> getDisplaySize() {
return std::pair<uint32_t, uint32_t>(width, height);
}
-// This value doesn't matter, as it's not read. TODO(b/199918329): Once we remove
-// GLESRenderEngine we can remove this, too.
-static constexpr const bool kUseFrameBufferCache = false;
-
static std::unique_ptr<RenderEngine> createRenderEngine(RenderEngine::RenderEngineType type) {
auto args = RenderEngineCreationArgs::Builder()
.setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
@@ -121,7 +113,6 @@ static std::unique_ptr<RenderEngine> createRenderEngine(RenderEngine::RenderEngi
.setSupportsBackgroundBlur(true)
.setContextPriority(RenderEngine::ContextPriority::REALTIME)
.setRenderEngineType(type)
- .setUseColorManagerment(true)
.build();
return RenderEngine::create(args);
}
@@ -173,10 +164,7 @@ static std::shared_ptr<ExternalTexture> copyBuffer(RenderEngine& re,
};
auto layers = std::vector<LayerSettings>{layer};
- sp<Fence> waitFence =
- re.drawLayers(display, layers, texture, kUseFrameBufferCache, base::unique_fd())
- .get()
- .value();
+ sp<Fence> waitFence = re.drawLayers(display, layers, texture, base::unique_fd()).get().value();
waitFence->waitForever(LOG_TAG);
return texture;
}
@@ -205,10 +193,8 @@ static void benchDrawLayers(RenderEngine& re, const std::vector<LayerSettings>&
// This loop starts and stops the timer.
for (auto _ : benchState) {
- sp<Fence> waitFence = re.drawLayers(display, layers, outputBuffer, kUseFrameBufferCache,
- base::unique_fd())
- .get()
- .value();
+ sp<Fence> waitFence =
+ re.drawLayers(display, layers, outputBuffer, base::unique_fd()).get().value();
waitFence->waitForever(LOG_TAG);
}
diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp
deleted file mode 100644
index 0d7df101f4..0000000000
--- a/libs/renderengine/gl/GLESRenderEngine.cpp
+++ /dev/null
@@ -1,1866 +0,0 @@
-/*
- * Copyright 2013 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_NDEBUG 0
-#include "EGL/egl.h"
-#undef LOG_TAG
-#define LOG_TAG "RenderEngine"
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include <sched.h>
-#include <cmath>
-#include <fstream>
-#include <sstream>
-#include <unordered_set>
-
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-#include <android-base/stringprintf.h>
-#include <cutils/compiler.h>
-#include <cutils/properties.h>
-#include <gui/DebugEGLImageTracker.h>
-#include <renderengine/Mesh.h>
-#include <renderengine/Texture.h>
-#include <renderengine/private/Description.h>
-#include <sync/sync.h>
-#include <ui/ColorSpace.h>
-#include <ui/DebugUtils.h>
-#include <ui/GraphicBuffer.h>
-#include <ui/Rect.h>
-#include <ui/Region.h>
-#include <utils/KeyedVector.h>
-#include <utils/Trace.h>
-#include "GLESRenderEngine.h"
-#include "GLExtensions.h"
-#include "GLFramebuffer.h"
-#include "GLImage.h"
-#include "GLShadowVertexGenerator.h"
-#include "Program.h"
-#include "ProgramCache.h"
-#include "filters/BlurFilter.h"
-
-bool checkGlError(const char* op, int lineNumber) {
- bool errorFound = false;
- GLint error = glGetError();
- while (error != GL_NO_ERROR) {
- errorFound = true;
- error = glGetError();
- ALOGV("after %s() (line # %d) glError (0x%x)\n", op, lineNumber, error);
- }
- return errorFound;
-}
-
-static constexpr bool outputDebugPPMs = false;
-
-void writePPM(const char* basename, GLuint width, GLuint height) {
- ALOGV("writePPM #%s: %d x %d", basename, width, height);
-
- std::vector<GLubyte> pixels(width * height * 4);
- std::vector<GLubyte> outBuffer(width * height * 3);
-
- // TODO(courtneygo): We can now have float formats, need
- // to remove this code or update to support.
- // Make returned pixels fit in uint32_t, one byte per component
- glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
- if (checkGlError(__FUNCTION__, __LINE__)) {
- return;
- }
-
- std::string filename(basename);
- filename.append(".ppm");
- std::ofstream file(filename.c_str(), std::ios::binary);
- if (!file.is_open()) {
- ALOGE("Unable to open file: %s", filename.c_str());
- ALOGE("You may need to do: \"adb shell setenforce 0\" to enable "
- "surfaceflinger to write debug images");
- return;
- }
-
- file << "P6\n";
- file << width << "\n";
- file << height << "\n";
- file << 255 << "\n";
-
- auto ptr = reinterpret_cast<char*>(pixels.data());
- auto outPtr = reinterpret_cast<char*>(outBuffer.data());
- for (int y = height - 1; y >= 0; y--) {
- char* data = ptr + y * width * sizeof(uint32_t);
-
- for (GLuint x = 0; x < width; x++) {
- // Only copy R, G and B components
- outPtr[0] = data[0];
- outPtr[1] = data[1];
- outPtr[2] = data[2];
- data += sizeof(uint32_t);
- outPtr += 3;
- }
- }
- file.write(reinterpret_cast<char*>(outBuffer.data()), outBuffer.size());
-}
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-class BindNativeBufferAsFramebuffer {
-public:
- BindNativeBufferAsFramebuffer(GLESRenderEngine& engine, ANativeWindowBuffer* buffer,
- const bool useFramebufferCache)
- : mEngine(engine), mFramebuffer(mEngine.getFramebufferForDrawing()), mStatus(NO_ERROR) {
- mStatus = mFramebuffer->setNativeWindowBuffer(buffer, mEngine.isProtected(),
- useFramebufferCache)
- ? mEngine.bindFrameBuffer(mFramebuffer)
- : NO_MEMORY;
- }
- ~BindNativeBufferAsFramebuffer() {
- mFramebuffer->setNativeWindowBuffer(nullptr, false, /*arbitrary*/ true);
- mEngine.unbindFrameBuffer(mFramebuffer);
- }
- status_t getStatus() const { return mStatus; }
-
-private:
- GLESRenderEngine& mEngine;
- Framebuffer* mFramebuffer;
- status_t mStatus;
-};
-
-using base::StringAppendF;
-using ui::Dataspace;
-
-static status_t selectConfigForAttribute(EGLDisplay dpy, EGLint const* attrs, EGLint attribute,
- EGLint wanted, EGLConfig* outConfig) {
- EGLint numConfigs = -1, n = 0;
- eglGetConfigs(dpy, nullptr, 0, &numConfigs);
- std::vector<EGLConfig> configs(numConfigs, EGL_NO_CONFIG_KHR);
- eglChooseConfig(dpy, attrs, configs.data(), configs.size(), &n);
- configs.resize(n);
-
- if (!configs.empty()) {
- if (attribute != EGL_NONE) {
- for (EGLConfig config : configs) {
- EGLint value = 0;
- eglGetConfigAttrib(dpy, config, attribute, &value);
- if (wanted == value) {
- *outConfig = config;
- return NO_ERROR;
- }
- }
- } else {
- // just pick the first one
- *outConfig = configs[0];
- return NO_ERROR;
- }
- }
-
- return NAME_NOT_FOUND;
-}
-
-static status_t selectEGLConfig(EGLDisplay display, EGLint format, EGLint renderableType,
- EGLConfig* config) {
- // select our EGLConfig. It must support EGL_RECORDABLE_ANDROID if
- // it is to be used with WIFI displays
- status_t err;
- EGLint wantedAttribute;
- EGLint wantedAttributeValue;
-
- std::vector<EGLint> attribs;
- if (renderableType) {
- const ui::PixelFormat pixelFormat = static_cast<ui::PixelFormat>(format);
- const bool is1010102 = pixelFormat == ui::PixelFormat::RGBA_1010102;
-
- // Default to 8 bits per channel.
- const EGLint tmpAttribs[] = {
- EGL_RENDERABLE_TYPE,
- renderableType,
- EGL_RECORDABLE_ANDROID,
- EGL_TRUE,
- EGL_SURFACE_TYPE,
- EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
- EGL_FRAMEBUFFER_TARGET_ANDROID,
- EGL_TRUE,
- EGL_RED_SIZE,
- is1010102 ? 10 : 8,
- EGL_GREEN_SIZE,
- is1010102 ? 10 : 8,
- EGL_BLUE_SIZE,
- is1010102 ? 10 : 8,
- EGL_ALPHA_SIZE,
- is1010102 ? 2 : 8,
- EGL_NONE,
- };
- std::copy(tmpAttribs, tmpAttribs + (sizeof(tmpAttribs) / sizeof(EGLint)),
- std::back_inserter(attribs));
- wantedAttribute = EGL_NONE;
- wantedAttributeValue = EGL_NONE;
- } else {
- // if no renderable type specified, fallback to a simplified query
- wantedAttribute = EGL_NATIVE_VISUAL_ID;
- wantedAttributeValue = format;
- }
-
- err = selectConfigForAttribute(display, attribs.data(), wantedAttribute, wantedAttributeValue,
- config);
- if (err == NO_ERROR) {
- EGLint caveat;
- if (eglGetConfigAttrib(display, *config, EGL_CONFIG_CAVEAT, &caveat))
- ALOGW_IF(caveat == EGL_SLOW_CONFIG, "EGL_SLOW_CONFIG selected!");
- }
-
- return err;
-}
-
-std::optional<RenderEngine::ContextPriority> GLESRenderEngine::createContextPriority(
- const RenderEngineCreationArgs& args) {
- if (!GLExtensions::getInstance().hasContextPriority()) {
- return std::nullopt;
- }
-
- switch (args.contextPriority) {
- case RenderEngine::ContextPriority::REALTIME:
- if (gl::GLExtensions::getInstance().hasRealtimePriority()) {
- return RenderEngine::ContextPriority::REALTIME;
- } else {
- ALOGI("Realtime priority unsupported, degrading gracefully to high priority");
- return RenderEngine::ContextPriority::HIGH;
- }
- case RenderEngine::ContextPriority::HIGH:
- case RenderEngine::ContextPriority::MEDIUM:
- case RenderEngine::ContextPriority::LOW:
- return args.contextPriority;
- default:
- return std::nullopt;
- }
-}
-
-std::unique_ptr<GLESRenderEngine> GLESRenderEngine::create(const RenderEngineCreationArgs& args) {
- // initialize EGL for the default display
- EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
- if (!eglInitialize(display, nullptr, nullptr)) {
- LOG_ALWAYS_FATAL("failed to initialize EGL. EGL error=0x%x", eglGetError());
- }
-
- const auto eglVersion = eglQueryString(display, EGL_VERSION);
- if (!eglVersion) {
- checkGlError(__FUNCTION__, __LINE__);
- LOG_ALWAYS_FATAL("eglQueryString(EGL_VERSION) failed");
- }
-
- // Use the Android impl to grab EGL_NV_context_priority_realtime
- const auto eglExtensions = eglQueryString(display, EGL_EXTENSIONS);
- if (!eglExtensions) {
- checkGlError(__FUNCTION__, __LINE__);
- LOG_ALWAYS_FATAL("eglQueryString(EGL_EXTENSIONS) failed");
- }
-
- GLExtensions& extensions = GLExtensions::getInstance();
- extensions.initWithEGLStrings(eglVersion, eglExtensions);
-
- // The code assumes that ES2 or later is available if this extension is
- // supported.
- EGLConfig config = EGL_NO_CONFIG;
- if (!extensions.hasNoConfigContext()) {
- config = chooseEglConfig(display, args.pixelFormat, /*logConfig*/ true);
- }
-
- const std::optional<RenderEngine::ContextPriority> priority = createContextPriority(args);
- EGLContext protectedContext = EGL_NO_CONTEXT;
- if (args.enableProtectedContext && extensions.hasProtectedContent()) {
- protectedContext =
- createEglContext(display, config, nullptr, priority, Protection::PROTECTED);
- ALOGE_IF(protectedContext == EGL_NO_CONTEXT, "Can't create protected context");
- }
-
- EGLContext ctxt =
- createEglContext(display, config, protectedContext, priority, Protection::UNPROTECTED);
-
- // if can't create a GL context, we can only abort.
- LOG_ALWAYS_FATAL_IF(ctxt == EGL_NO_CONTEXT, "EGLContext creation failed");
-
- EGLSurface stub = EGL_NO_SURFACE;
- if (!extensions.hasSurfacelessContext()) {
- stub = createStubEglPbufferSurface(display, config, args.pixelFormat,
- Protection::UNPROTECTED);
- LOG_ALWAYS_FATAL_IF(stub == EGL_NO_SURFACE, "can't create stub pbuffer");
- }
- EGLBoolean success = eglMakeCurrent(display, stub, stub, ctxt);
- LOG_ALWAYS_FATAL_IF(!success, "can't make stub pbuffer current");
- extensions.initWithGLStrings(glGetString(GL_VENDOR), glGetString(GL_RENDERER),
- glGetString(GL_VERSION), glGetString(GL_EXTENSIONS));
-
- EGLSurface protectedStub = EGL_NO_SURFACE;
- if (protectedContext != EGL_NO_CONTEXT && !extensions.hasSurfacelessContext()) {
- protectedStub = createStubEglPbufferSurface(display, config, args.pixelFormat,
- Protection::PROTECTED);
- ALOGE_IF(protectedStub == EGL_NO_SURFACE, "can't create protected stub pbuffer");
- }
-
- // now figure out what version of GL did we actually get
- GlesVersion version = parseGlesVersion(extensions.getVersion());
-
- LOG_ALWAYS_FATAL_IF(args.supportsBackgroundBlur && version < GLES_VERSION_3_0,
- "Blurs require OpenGL ES 3.0. Please unset ro.surface_flinger.supports_background_blur");
-
- // initialize the renderer while GL is current
- std::unique_ptr<GLESRenderEngine> engine;
- switch (version) {
- case GLES_VERSION_1_0:
- case GLES_VERSION_1_1:
- LOG_ALWAYS_FATAL("SurfaceFlinger requires OpenGL ES 2.0 minimum to run.");
- break;
- case GLES_VERSION_2_0:
- case GLES_VERSION_3_0:
- engine = std::make_unique<GLESRenderEngine>(args, display, config, ctxt, stub,
- protectedContext, protectedStub);
- break;
- }
-
- ALOGI("OpenGL ES informations:");
- ALOGI("vendor : %s", extensions.getVendor());
- ALOGI("renderer : %s", extensions.getRenderer());
- ALOGI("version : %s", extensions.getVersion());
- ALOGI("extensions: %s", extensions.getExtensions());
- ALOGI("GL_MAX_TEXTURE_SIZE = %zu", engine->getMaxTextureSize());
- ALOGI("GL_MAX_VIEWPORT_DIMS = %zu", engine->getMaxViewportDims());
- return engine;
-}
-
-EGLConfig GLESRenderEngine::chooseEglConfig(EGLDisplay display, int format, bool logConfig) {
- status_t err;
- EGLConfig config;
-
- // First try to get an ES3 config
- err = selectEGLConfig(display, format, EGL_OPENGL_ES3_BIT, &config);
- if (err != NO_ERROR) {
- // If ES3 fails, try to get an ES2 config
- err = selectEGLConfig(display, format, EGL_OPENGL_ES2_BIT, &config);
- if (err != NO_ERROR) {
- // If ES2 still doesn't work, probably because we're on the emulator.
- // try a simplified query
- ALOGW("no suitable EGLConfig found, trying a simpler query");
- err = selectEGLConfig(display, format, 0, &config);
- if (err != NO_ERROR) {
- // this EGL is too lame for android
- LOG_ALWAYS_FATAL("no suitable EGLConfig found, giving up");
- }
- }
- }
-
- if (logConfig) {
- // print some debugging info
- EGLint r, g, b, a;
- eglGetConfigAttrib(display, config, EGL_RED_SIZE, &r);
- eglGetConfigAttrib(display, config, EGL_GREEN_SIZE, &g);
- eglGetConfigAttrib(display, config, EGL_BLUE_SIZE, &b);
- eglGetConfigAttrib(display, config, EGL_ALPHA_SIZE, &a);
- ALOGI("EGL information:");
- ALOGI("vendor : %s", eglQueryString(display, EGL_VENDOR));
- ALOGI("version : %s", eglQueryString(display, EGL_VERSION));
- ALOGI("extensions: %s", eglQueryString(display, EGL_EXTENSIONS));
- ALOGI("Client API: %s", eglQueryString(display, EGL_CLIENT_APIS) ?: "Not Supported");
- ALOGI("EGLSurface: %d-%d-%d-%d, config=%p", r, g, b, a, config);
- }
-
- return config;
-}
-
-GLESRenderEngine::GLESRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display,
- EGLConfig config, EGLContext ctxt, EGLSurface stub,
- EGLContext protectedContext, EGLSurface protectedStub)
- : RenderEngine(args.renderEngineType),
- mEGLDisplay(display),
- mEGLConfig(config),
- mEGLContext(ctxt),
- mStubSurface(stub),
- mProtectedEGLContext(protectedContext),
- mProtectedStubSurface(protectedStub),
- mVpWidth(0),
- mVpHeight(0),
- mFramebufferImageCacheSize(args.imageCacheSize),
- mUseColorManagement(args.useColorManagement),
- mPrecacheToneMapperShaderOnly(args.precacheToneMapperShaderOnly) {
- glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
- glGetIntegerv(GL_MAX_VIEWPORT_DIMS, mMaxViewportDims);
-
- glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
- glPixelStorei(GL_PACK_ALIGNMENT, 4);
-
- // Initialize protected EGL Context.
- if (mProtectedEGLContext != EGL_NO_CONTEXT) {
- EGLBoolean success = eglMakeCurrent(display, mProtectedStubSurface, mProtectedStubSurface,
- mProtectedEGLContext);
- ALOGE_IF(!success, "can't make protected context current");
- glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
- glPixelStorei(GL_PACK_ALIGNMENT, 4);
- success = eglMakeCurrent(display, mStubSurface, mStubSurface, mEGLContext);
- LOG_ALWAYS_FATAL_IF(!success, "can't make default context current");
- }
-
- // mColorBlindnessCorrection = M;
-
- if (mUseColorManagement) {
- const ColorSpace srgb(ColorSpace::sRGB());
- const ColorSpace displayP3(ColorSpace::DisplayP3());
- const ColorSpace bt2020(ColorSpace::BT2020());
-
- // no chromatic adaptation needed since all color spaces use D65 for their white points.
- mSrgbToXyz = mat4(srgb.getRGBtoXYZ());
- mDisplayP3ToXyz = mat4(displayP3.getRGBtoXYZ());
- mBt2020ToXyz = mat4(bt2020.getRGBtoXYZ());
- mXyzToSrgb = mat4(srgb.getXYZtoRGB());
- mXyzToDisplayP3 = mat4(displayP3.getXYZtoRGB());
- mXyzToBt2020 = mat4(bt2020.getXYZtoRGB());
-
- // Compute sRGB to Display P3 and BT2020 transform matrix.
- // NOTE: For now, we are limiting output wide color space support to
- // Display-P3 and BT2020 only.
- mSrgbToDisplayP3 = mXyzToDisplayP3 * mSrgbToXyz;
- mSrgbToBt2020 = mXyzToBt2020 * mSrgbToXyz;
-
- // Compute Display P3 to sRGB and BT2020 transform matrix.
- mDisplayP3ToSrgb = mXyzToSrgb * mDisplayP3ToXyz;
- mDisplayP3ToBt2020 = mXyzToBt2020 * mDisplayP3ToXyz;
-
- // Compute BT2020 to sRGB and Display P3 transform matrix
- mBt2020ToSrgb = mXyzToSrgb * mBt2020ToXyz;
- mBt2020ToDisplayP3 = mXyzToDisplayP3 * mBt2020ToXyz;
- }
-
- char value[PROPERTY_VALUE_MAX];
- property_get("debug.egl.traceGpuCompletion", value, "0");
- if (atoi(value)) {
- mTraceGpuCompletion = true;
- mFlushTracer = std::make_unique<FlushTracer>(this);
- }
-
- if (args.supportsBackgroundBlur) {
- mBlurFilter = new BlurFilter(*this);
- checkErrors("BlurFilter creation");
- }
-
- mImageManager = std::make_unique<ImageManager>(this);
- mImageManager->initThread();
- mDrawingBuffer = createFramebuffer();
- sp<GraphicBuffer> buf =
- sp<GraphicBuffer>::make(1, 1, PIXEL_FORMAT_RGBA_8888, 1,
- GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE,
- "placeholder");
-
- const status_t err = buf->initCheck();
- if (err != OK) {
- ALOGE("Error allocating placeholder buffer: %d", err);
- return;
- }
- mPlaceholderBuffer = buf.get();
- EGLint attributes[] = {
- EGL_NONE,
- };
- mPlaceholderImage = eglCreateImageKHR(mEGLDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
- mPlaceholderBuffer, attributes);
- ALOGE_IF(mPlaceholderImage == EGL_NO_IMAGE_KHR, "Failed to create placeholder image: %#x",
- eglGetError());
-
- mShadowTexture = std::make_unique<GLShadowTexture>();
-}
-
-GLESRenderEngine::~GLESRenderEngine() {
- // Destroy the image manager first.
- mImageManager = nullptr;
- mShadowTexture = nullptr;
- cleanFramebufferCache();
- ProgramCache::getInstance().purgeCaches();
- std::lock_guard<std::mutex> lock(mRenderingMutex);
- glDisableVertexAttribArray(Program::position);
- unbindFrameBuffer(mDrawingBuffer.get());
- mDrawingBuffer = nullptr;
- eglDestroyImageKHR(mEGLDisplay, mPlaceholderImage);
- mImageCache.clear();
- if (mStubSurface != EGL_NO_SURFACE) {
- eglDestroySurface(mEGLDisplay, mStubSurface);
- }
- if (mProtectedStubSurface != EGL_NO_SURFACE) {
- eglDestroySurface(mEGLDisplay, mProtectedStubSurface);
- }
- if (mEGLContext != EGL_NO_CONTEXT) {
- eglDestroyContext(mEGLDisplay, mEGLContext);
- }
- if (mProtectedEGLContext != EGL_NO_CONTEXT) {
- eglDestroyContext(mEGLDisplay, mProtectedEGLContext);
- }
- eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- eglTerminate(mEGLDisplay);
- eglReleaseThread();
-}
-
-std::unique_ptr<Framebuffer> GLESRenderEngine::createFramebuffer() {
- return std::make_unique<GLFramebuffer>(*this);
-}
-
-std::unique_ptr<Image> GLESRenderEngine::createImage() {
- return std::make_unique<GLImage>(*this);
-}
-
-Framebuffer* GLESRenderEngine::getFramebufferForDrawing() {
- return mDrawingBuffer.get();
-}
-
-std::future<void> GLESRenderEngine::primeCache() {
- ProgramCache::getInstance().primeCache(mInProtectedContext ? mProtectedEGLContext : mEGLContext,
- mUseColorManagement, mPrecacheToneMapperShaderOnly);
- return {};
-}
-
-base::unique_fd GLESRenderEngine::flush() {
- ATRACE_CALL();
- if (!GLExtensions::getInstance().hasNativeFenceSync()) {
- return base::unique_fd();
- }
-
- EGLSyncKHR sync = eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
- if (sync == EGL_NO_SYNC_KHR) {
- ALOGW("failed to create EGL native fence sync: %#x", eglGetError());
- return base::unique_fd();
- }
-
- // native fence fd will not be populated until flush() is done.
- glFlush();
-
- // get the fence fd
- base::unique_fd fenceFd(eglDupNativeFenceFDANDROID(mEGLDisplay, sync));
- eglDestroySyncKHR(mEGLDisplay, sync);
- if (fenceFd == EGL_NO_NATIVE_FENCE_FD_ANDROID) {
- ALOGW("failed to dup EGL native fence sync: %#x", eglGetError());
- }
-
- // Only trace if we have a valid fence, as current usage falls back to
- // calling finish() if the fence fd is invalid.
- if (CC_UNLIKELY(mTraceGpuCompletion && mFlushTracer) && fenceFd.get() >= 0) {
- mFlushTracer->queueSync(eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_FENCE_KHR, nullptr));
- }
-
- return fenceFd;
-}
-
-bool GLESRenderEngine::finish() {
- ATRACE_CALL();
- if (!GLExtensions::getInstance().hasFenceSync()) {
- ALOGW("no synchronization support");
- return false;
- }
-
- EGLSyncKHR sync = eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_FENCE_KHR, nullptr);
- if (sync == EGL_NO_SYNC_KHR) {
- ALOGW("failed to create EGL fence sync: %#x", eglGetError());
- return false;
- }
-
- if (CC_UNLIKELY(mTraceGpuCompletion && mFlushTracer)) {
- mFlushTracer->queueSync(eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_FENCE_KHR, nullptr));
- }
-
- return waitSync(sync, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR);
-}
-
-bool GLESRenderEngine::waitSync(EGLSyncKHR sync, EGLint flags) {
- EGLint result = eglClientWaitSyncKHR(mEGLDisplay, sync, flags, 2000000000 /*2 sec*/);
- EGLint error = eglGetError();
- eglDestroySyncKHR(mEGLDisplay, sync);
- if (result != EGL_CONDITION_SATISFIED_KHR) {
- if (result == EGL_TIMEOUT_EXPIRED_KHR) {
- ALOGW("fence wait timed out");
- } else {
- ALOGW("error waiting on EGL fence: %#x", error);
- }
- return false;
- }
-
- return true;
-}
-
-bool GLESRenderEngine::waitFence(base::unique_fd fenceFd) {
- if (!GLExtensions::getInstance().hasNativeFenceSync() ||
- !GLExtensions::getInstance().hasWaitSync()) {
- return false;
- }
-
- // release the fd and transfer the ownership to EGLSync
- EGLint attribs[] = {EGL_SYNC_NATIVE_FENCE_FD_ANDROID, fenceFd.release(), EGL_NONE};
- EGLSyncKHR sync = eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, attribs);
- if (sync == EGL_NO_SYNC_KHR) {
- ALOGE("failed to create EGL native fence sync: %#x", eglGetError());
- return false;
- }
-
- // XXX: The spec draft is inconsistent as to whether this should return an
- // EGLint or void. Ignore the return value for now, as it's not strictly
- // needed.
- eglWaitSyncKHR(mEGLDisplay, sync, 0);
- EGLint error = eglGetError();
- eglDestroySyncKHR(mEGLDisplay, sync);
- if (error != EGL_SUCCESS) {
- ALOGE("failed to wait for EGL native fence sync: %#x", error);
- return false;
- }
-
- return true;
-}
-
-void GLESRenderEngine::clearWithColor(float red, float green, float blue, float alpha) {
- ATRACE_CALL();
- glDisable(GL_BLEND);
- glClearColor(red, green, blue, alpha);
- glClear(GL_COLOR_BUFFER_BIT);
-}
-
-void GLESRenderEngine::fillRegionWithColor(const Region& region, float red, float green, float blue,
- float alpha) {
- size_t c;
- Rect const* r = region.getArray(&c);
- Mesh mesh = Mesh::Builder()
- .setPrimitive(Mesh::TRIANGLES)
- .setVertices(c * 6 /* count */, 2 /* size */)
- .build();
- Mesh::VertexArray<vec2> position(mesh.getPositionArray<vec2>());
- for (size_t i = 0; i < c; i++, r++) {
- position[i * 6 + 0].x = r->left;
- position[i * 6 + 0].y = r->top;
- position[i * 6 + 1].x = r->left;
- position[i * 6 + 1].y = r->bottom;
- position[i * 6 + 2].x = r->right;
- position[i * 6 + 2].y = r->bottom;
- position[i * 6 + 3].x = r->left;
- position[i * 6 + 3].y = r->top;
- position[i * 6 + 4].x = r->right;
- position[i * 6 + 4].y = r->bottom;
- position[i * 6 + 5].x = r->right;
- position[i * 6 + 5].y = r->top;
- }
- setupFillWithColor(red, green, blue, alpha);
- drawMesh(mesh);
-}
-
-void GLESRenderEngine::setScissor(const Rect& region) {
- glScissor(region.left, region.top, region.getWidth(), region.getHeight());
- glEnable(GL_SCISSOR_TEST);
-}
-
-void GLESRenderEngine::disableScissor() {
- glDisable(GL_SCISSOR_TEST);
-}
-
-void GLESRenderEngine::genTextures(size_t count, uint32_t* names) {
- glGenTextures(count, names);
-}
-
-void GLESRenderEngine::deleteTextures(size_t count, uint32_t const* names) {
- for (int i = 0; i < count; ++i) {
- mTextureView.erase(names[i]);
- }
- glDeleteTextures(count, names);
-}
-
-void GLESRenderEngine::bindExternalTextureImage(uint32_t texName, const Image& image) {
- ATRACE_CALL();
- const GLImage& glImage = static_cast<const GLImage&>(image);
- const GLenum target = GL_TEXTURE_EXTERNAL_OES;
-
- glBindTexture(target, texName);
- if (glImage.getEGLImage() != EGL_NO_IMAGE_KHR) {
- glEGLImageTargetTexture2DOES(target, static_cast<GLeglImageOES>(glImage.getEGLImage()));
- }
-}
-
-void GLESRenderEngine::bindExternalTextureBuffer(uint32_t texName, const sp<GraphicBuffer>& buffer,
- const sp<Fence>& bufferFence) {
- ATRACE_CALL();
-
- bool found = false;
- {
- std::lock_guard<std::mutex> lock(mRenderingMutex);
- auto cachedImage = mImageCache.find(buffer->getId());
- found = (cachedImage != mImageCache.end());
- }
-
- // If we couldn't find the image in the cache at this time, then either
- // SurfaceFlinger messed up registering the buffer ahead of time or we got
- // backed up creating other EGLImages.
- if (!found) {
- status_t cacheResult = mImageManager->cache(buffer);
- if (cacheResult != NO_ERROR) {
- ALOGE("Error with caching buffer: %d", cacheResult);
- return;
- }
- }
-
- // Whether or not we needed to cache, re-check mImageCache to make sure that
- // there's an EGLImage. The current threading model guarantees that we don't
- // destroy a cached image until it's really not needed anymore (i.e. this
- // function should not be called), so the only possibility is that something
- // terrible went wrong and we should just bind something and move on.
- {
- std::lock_guard<std::mutex> lock(mRenderingMutex);
- auto cachedImage = mImageCache.find(buffer->getId());
-
- if (cachedImage == mImageCache.end()) {
- // We failed creating the image if we got here, so bail out.
- ALOGE("Failed to create an EGLImage when rendering");
- bindExternalTextureImage(texName, *createImage());
- return;
- }
-
- bindExternalTextureImage(texName, *cachedImage->second);
- mTextureView.insert_or_assign(texName, buffer->getId());
- }
-
- // Wait for the new buffer to be ready.
- if (bufferFence != nullptr && bufferFence->isValid()) {
- if (GLExtensions::getInstance().hasWaitSync()) {
- base::unique_fd fenceFd(bufferFence->dup());
- if (fenceFd == -1) {
- ALOGE("error dup'ing fence fd: %d", errno);
- return;
- }
- if (!waitFence(std::move(fenceFd))) {
- ALOGE("failed to wait on fence fd");
- return;
- }
- } else {
- status_t err = bufferFence->waitForever("RenderEngine::bindExternalTextureBuffer");
- if (err != NO_ERROR) {
- ALOGE("error waiting for fence: %d", err);
- return;
- }
- }
- }
-
- return;
-}
-
-void GLESRenderEngine::mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
- bool /*isRenderable*/) {
- ATRACE_CALL();
- mImageManager->cacheAsync(buffer, nullptr);
-}
-
-std::shared_ptr<ImageManager::Barrier> GLESRenderEngine::cacheExternalTextureBufferForTesting(
- const sp<GraphicBuffer>& buffer) {
- auto barrier = std::make_shared<ImageManager::Barrier>();
- mImageManager->cacheAsync(buffer, barrier);
- return barrier;
-}
-
-status_t GLESRenderEngine::cacheExternalTextureBufferInternal(const sp<GraphicBuffer>& buffer) {
- if (buffer == nullptr) {
- return BAD_VALUE;
- }
-
- {
- std::lock_guard<std::mutex> lock(mRenderingMutex);
- if (mImageCache.count(buffer->getId()) > 0) {
- // If there's already an image then fail fast here.
- return NO_ERROR;
- }
- }
- ATRACE_CALL();
-
- // Create the image without holding a lock so that we don't block anything.
- std::unique_ptr<Image> newImage = createImage();
-
- bool created = newImage->setNativeWindowBuffer(buffer->getNativeBuffer(),
- buffer->getUsage() & GRALLOC_USAGE_PROTECTED);
- if (!created) {
- ALOGE("Failed to create image. id=%" PRIx64 " size=%ux%u st=%u usage=%#" PRIx64 " fmt=%d",
- buffer->getId(), buffer->getWidth(), buffer->getHeight(), buffer->getStride(),
- buffer->getUsage(), buffer->getPixelFormat());
- return NO_INIT;
- }
-
- {
- std::lock_guard<std::mutex> lock(mRenderingMutex);
- if (mImageCache.count(buffer->getId()) > 0) {
- // In theory it's possible for another thread to recache the image,
- // so bail out if another thread won.
- return NO_ERROR;
- }
- mImageCache.insert(std::make_pair(buffer->getId(), std::move(newImage)));
- }
-
- return NO_ERROR;
-}
-
-void GLESRenderEngine::unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) {
- mImageManager->releaseAsync(buffer->getId(), nullptr);
-}
-
-std::shared_ptr<ImageManager::Barrier> GLESRenderEngine::unbindExternalTextureBufferForTesting(
- uint64_t bufferId) {
- auto barrier = std::make_shared<ImageManager::Barrier>();
- mImageManager->releaseAsync(bufferId, barrier);
- return barrier;
-}
-
-void GLESRenderEngine::unbindExternalTextureBufferInternal(uint64_t bufferId) {
- std::unique_ptr<Image> image;
- {
- std::lock_guard<std::mutex> lock(mRenderingMutex);
- const auto& cachedImage = mImageCache.find(bufferId);
-
- if (cachedImage != mImageCache.end()) {
- ALOGV("Destroying image for buffer: %" PRIu64, bufferId);
- // Move the buffer out of cache first, so that we can destroy
- // without holding the cache's lock.
- image = std::move(cachedImage->second);
- mImageCache.erase(bufferId);
- return;
- }
- }
- ALOGV("Failed to find image for buffer: %" PRIu64, bufferId);
-}
-
-int GLESRenderEngine::getContextPriority() {
- int value;
- eglQueryContext(mEGLDisplay, mEGLContext, EGL_CONTEXT_PRIORITY_LEVEL_IMG, &value);
- return value;
-}
-
-FloatRect GLESRenderEngine::setupLayerCropping(const LayerSettings& layer, Mesh& mesh) {
- // Translate win by the rounded corners rect coordinates, to have all values in
- // layer coordinate space.
- FloatRect cropWin = layer.geometry.boundaries;
- const FloatRect& roundedCornersCrop = layer.geometry.roundedCornersCrop;
- cropWin.left -= roundedCornersCrop.left;
- cropWin.right -= roundedCornersCrop.left;
- cropWin.top -= roundedCornersCrop.top;
- cropWin.bottom -= roundedCornersCrop.top;
- Mesh::VertexArray<vec2> cropCoords(mesh.getCropCoordArray<vec2>());
- cropCoords[0] = vec2(cropWin.left, cropWin.top);
- cropCoords[1] = vec2(cropWin.left, cropWin.top + cropWin.getHeight());
- cropCoords[2] = vec2(cropWin.right, cropWin.top + cropWin.getHeight());
- cropCoords[3] = vec2(cropWin.right, cropWin.top);
-
- setupCornerRadiusCropSize(roundedCornersCrop.getWidth(), roundedCornersCrop.getHeight());
- return cropWin;
-}
-
-void GLESRenderEngine::handleRoundedCorners(const DisplaySettings& display,
- const LayerSettings& layer, const Mesh& mesh) {
- // We separate the layer into 3 parts essentially, such that we only turn on blending for the
- // top rectangle and the bottom rectangle, and turn off blending for the middle rectangle.
- FloatRect bounds = layer.geometry.roundedCornersCrop;
-
- // Explicitly compute the transform from the clip rectangle to the physical
- // display. Normally, this is done in glViewport but we explicitly compute
- // it here so that we can get the scissor bounds correct.
- const Rect& source = display.clip;
- const Rect& destination = display.physicalDisplay;
- // Here we compute the following transform:
- // 1. Translate the top left corner of the source clip to (0, 0)
- // 2. Rotate the clip rectangle about the origin in accordance with the
- // orientation flag
- // 3. Translate the top left corner back to the origin.
- // 4. Scale the clip rectangle to the destination rectangle dimensions
- // 5. Translate the top left corner to the destination rectangle's top left
- // corner.
- const mat4 translateSource = mat4::translate(vec4(-source.left, -source.top, 0, 1));
- mat4 rotation;
- int displacementX = 0;
- int displacementY = 0;
- float destinationWidth = static_cast<float>(destination.getWidth());
- float destinationHeight = static_cast<float>(destination.getHeight());
- float sourceWidth = static_cast<float>(source.getWidth());
- float sourceHeight = static_cast<float>(source.getHeight());
- const float rot90InRadians = 2.0f * static_cast<float>(M_PI) / 4.0f;
- switch (display.orientation) {
- case ui::Transform::ROT_90:
- rotation = mat4::rotate(rot90InRadians, vec3(0, 0, 1));
- displacementX = source.getHeight();
- std::swap(sourceHeight, sourceWidth);
- break;
- case ui::Transform::ROT_180:
- rotation = mat4::rotate(rot90InRadians * 2.0f, vec3(0, 0, 1));
- displacementY = source.getHeight();
- displacementX = source.getWidth();
- break;
- case ui::Transform::ROT_270:
- rotation = mat4::rotate(rot90InRadians * 3.0f, vec3(0, 0, 1));
- displacementY = source.getWidth();
- std::swap(sourceHeight, sourceWidth);
- break;
- default:
- break;
- }
-
- const mat4 intermediateTranslation = mat4::translate(vec4(displacementX, displacementY, 0, 1));
- const mat4 scale = mat4::scale(
- vec4(destinationWidth / sourceWidth, destinationHeight / sourceHeight, 1, 1));
- const mat4 translateDestination =
- mat4::translate(vec4(destination.left, destination.top, 0, 1));
- const mat4 globalTransform =
- translateDestination * scale * intermediateTranslation * rotation * translateSource;
-
- const mat4 transformMatrix = globalTransform * layer.geometry.positionTransform;
- const vec4 leftTopCoordinate(bounds.left, bounds.top, 1.0, 1.0);
- const vec4 rightBottomCoordinate(bounds.right, bounds.bottom, 1.0, 1.0);
- const vec4 leftTopCoordinateInBuffer = transformMatrix * leftTopCoordinate;
- const vec4 rightBottomCoordinateInBuffer = transformMatrix * rightBottomCoordinate;
- bounds = FloatRect(std::min(leftTopCoordinateInBuffer[0], rightBottomCoordinateInBuffer[0]),
- std::min(leftTopCoordinateInBuffer[1], rightBottomCoordinateInBuffer[1]),
- std::max(leftTopCoordinateInBuffer[0], rightBottomCoordinateInBuffer[0]),
- std::max(leftTopCoordinateInBuffer[1], rightBottomCoordinateInBuffer[1]));
-
- // Finally, we cut the layer into 3 parts, with top and bottom parts having rounded corners
- // and the middle part without rounded corners.
- const int32_t radius = ceil(
- (layer.geometry.roundedCornersRadius.x + layer.geometry.roundedCornersRadius.y) / 2.0);
- const Rect topRect(bounds.left, bounds.top, bounds.right, bounds.top + radius);
- setScissor(topRect);
- drawMesh(mesh);
- const Rect bottomRect(bounds.left, bounds.bottom - radius, bounds.right, bounds.bottom);
- setScissor(bottomRect);
- drawMesh(mesh);
-
- // The middle part of the layer can turn off blending.
- if (topRect.bottom < bottomRect.top) {
- const Rect middleRect(bounds.left, bounds.top + radius, bounds.right,
- bounds.bottom - radius);
- setScissor(middleRect);
- mState.cornerRadius = 0.0;
- disableBlending();
- drawMesh(mesh);
- }
- disableScissor();
-}
-
-status_t GLESRenderEngine::bindFrameBuffer(Framebuffer* framebuffer) {
- ATRACE_CALL();
- GLFramebuffer* glFramebuffer = static_cast<GLFramebuffer*>(framebuffer);
- EGLImageKHR eglImage = glFramebuffer->getEGLImage();
- uint32_t textureName = glFramebuffer->getTextureName();
- uint32_t framebufferName = glFramebuffer->getFramebufferName();
-
- // Bind the texture and turn our EGLImage into a texture
- glBindTexture(GL_TEXTURE_2D, textureName);
- glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)eglImage);
-
- // Bind the Framebuffer to render into
- glBindFramebuffer(GL_FRAMEBUFFER, framebufferName);
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureName, 0);
-
- uint32_t glStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
- ALOGE_IF(glStatus != GL_FRAMEBUFFER_COMPLETE_OES, "glCheckFramebufferStatusOES error %d",
- glStatus);
-
- return glStatus == GL_FRAMEBUFFER_COMPLETE_OES ? NO_ERROR : BAD_VALUE;
-}
-
-void GLESRenderEngine::unbindFrameBuffer(Framebuffer* /*framebuffer*/) {
- ATRACE_CALL();
-
- // back to main framebuffer
- glBindFramebuffer(GL_FRAMEBUFFER, 0);
-}
-
-bool GLESRenderEngine::canSkipPostRenderCleanup() const {
- return mPriorResourcesCleaned ||
- (mLastDrawFence != nullptr && mLastDrawFence->getStatus() != Fence::Status::Signaled);
-}
-
-void GLESRenderEngine::cleanupPostRender() {
- ATRACE_CALL();
-
- if (canSkipPostRenderCleanup()) {
- // If we don't have a prior frame needing cleanup, then don't do anything.
- return;
- }
-
- // Bind the texture to placeholder so that backing image data can be freed.
- GLFramebuffer* glFramebuffer = static_cast<GLFramebuffer*>(getFramebufferForDrawing());
- glFramebuffer->allocateBuffers(1, 1, mPlaceholderDrawBuffer);
-
- // Release the cached fence here, so that we don't churn reallocations when
- // we could no-op repeated calls of this method instead.
- mLastDrawFence = nullptr;
- mPriorResourcesCleaned = true;
-}
-
-void GLESRenderEngine::cleanFramebufferCache() {
- std::lock_guard<std::mutex> lock(mFramebufferImageCacheMutex);
- // Bind the texture to placeholder so that backing image data can be freed.
- GLFramebuffer* glFramebuffer = static_cast<GLFramebuffer*>(getFramebufferForDrawing());
- glFramebuffer->allocateBuffers(1, 1, mPlaceholderDrawBuffer);
-
- while (!mFramebufferImageCache.empty()) {
- EGLImageKHR expired = mFramebufferImageCache.front().second;
- mFramebufferImageCache.pop_front();
- eglDestroyImageKHR(mEGLDisplay, expired);
- DEBUG_EGL_IMAGE_TRACKER_DESTROY();
- }
-}
-
-void GLESRenderEngine::checkErrors() const {
- checkErrors(nullptr);
-}
-
-void GLESRenderEngine::checkErrors(const char* tag) const {
- do {
- // there could be more than one error flag
- GLenum error = glGetError();
- if (error == GL_NO_ERROR) break;
- if (tag == nullptr) {
- ALOGE("GL error 0x%04x", int(error));
- } else {
- ALOGE("GL error: %s -> 0x%04x", tag, int(error));
- }
- } while (true);
-}
-
-bool GLESRenderEngine::supportsProtectedContent() const {
- return mProtectedEGLContext != EGL_NO_CONTEXT;
-}
-
-void GLESRenderEngine::useProtectedContext(bool useProtectedContext) {
- if (useProtectedContext == mInProtectedContext ||
- (useProtectedContext && !supportsProtectedContent())) {
- return;
- }
-
- const EGLSurface surface = useProtectedContext ? mProtectedStubSurface : mStubSurface;
- const EGLContext context = useProtectedContext ? mProtectedEGLContext : mEGLContext;
- if (eglMakeCurrent(mEGLDisplay, surface, surface, context) == EGL_TRUE) {
- mInProtectedContext = useProtectedContext;
- }
-}
-EGLImageKHR GLESRenderEngine::createFramebufferImageIfNeeded(ANativeWindowBuffer* nativeBuffer,
- bool isProtected,
- bool useFramebufferCache) {
- sp<GraphicBuffer> graphicBuffer = GraphicBuffer::from(nativeBuffer);
- if (useFramebufferCache) {
- std::lock_guard<std::mutex> lock(mFramebufferImageCacheMutex);
- for (const auto& image : mFramebufferImageCache) {
- if (image.first == graphicBuffer->getId()) {
- return image.second;
- }
- }
- }
- EGLint attributes[] = {
- isProtected ? EGL_PROTECTED_CONTENT_EXT : EGL_NONE,
- isProtected ? EGL_TRUE : EGL_NONE,
- EGL_NONE,
- };
- EGLImageKHR image = eglCreateImageKHR(mEGLDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
- nativeBuffer, attributes);
- if (useFramebufferCache) {
- if (image != EGL_NO_IMAGE_KHR) {
- std::lock_guard<std::mutex> lock(mFramebufferImageCacheMutex);
- if (mFramebufferImageCache.size() >= mFramebufferImageCacheSize) {
- EGLImageKHR expired = mFramebufferImageCache.front().second;
- mFramebufferImageCache.pop_front();
- eglDestroyImageKHR(mEGLDisplay, expired);
- DEBUG_EGL_IMAGE_TRACKER_DESTROY();
- }
- mFramebufferImageCache.push_back({graphicBuffer->getId(), image});
- }
- }
-
- if (image != EGL_NO_IMAGE_KHR) {
- DEBUG_EGL_IMAGE_TRACKER_CREATE();
- }
- return image;
-}
-
-void GLESRenderEngine::drawLayersInternal(
- const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
- const DisplaySettings& display, const std::vector<LayerSettings>& layers,
- const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
- base::unique_fd&& bufferFence) {
- ATRACE_CALL();
- if (layers.empty()) {
- ALOGV("Drawing empty layer stack");
- resultPromise->set_value(Fence::NO_FENCE);
- return;
- }
-
- if (bufferFence.get() >= 0) {
- // Duplicate the fence for passing to waitFence.
- base::unique_fd bufferFenceDup(dup(bufferFence.get()));
- if (bufferFenceDup < 0 || !waitFence(std::move(bufferFenceDup))) {
- ATRACE_NAME("Waiting before draw");
- sync_wait(bufferFence.get(), -1);
- }
- }
-
- if (buffer == nullptr) {
- ALOGE("No output buffer provided. Aborting GPU composition.");
- resultPromise->set_value(base::unexpected(BAD_VALUE));
- return;
- }
-
- validateOutputBufferUsage(buffer->getBuffer());
-
- std::unique_ptr<BindNativeBufferAsFramebuffer> fbo;
- // Gathering layers that requested blur, we'll need them to decide when to render to an
- // offscreen buffer, and when to render to the native buffer.
- std::deque<const LayerSettings> blurLayers;
- if (CC_LIKELY(mBlurFilter != nullptr)) {
- for (const auto& layer : layers) {
- if (layer.backgroundBlurRadius > 0) {
- blurLayers.push_back(layer);
- }
- }
- }
- const auto blurLayersSize = blurLayers.size();
-
- if (blurLayersSize == 0) {
- fbo = std::make_unique<BindNativeBufferAsFramebuffer>(*this,
- buffer->getBuffer()
- .get()
- ->getNativeBuffer(),
- useFramebufferCache);
- if (fbo->getStatus() != NO_ERROR) {
- ALOGE("Failed to bind framebuffer! Aborting GPU composition for buffer (%p).",
- buffer->getBuffer()->handle);
- checkErrors();
- resultPromise->set_value(base::unexpected(fbo->getStatus()));
- return;
- }
- setViewportAndProjection(display.physicalDisplay, display.clip);
- } else {
- setViewportAndProjection(display.physicalDisplay, display.clip);
- auto status =
- mBlurFilter->setAsDrawTarget(display, blurLayers.front().backgroundBlurRadius);
- if (status != NO_ERROR) {
- ALOGE("Failed to prepare blur filter! Aborting GPU composition for buffer (%p).",
- buffer->getBuffer()->handle);
- checkErrors();
- resultPromise->set_value(base::unexpected(status));
- return;
- }
- }
-
- // clear the entire buffer, sometimes when we reuse buffers we'd persist
- // ghost images otherwise.
- // we also require a full transparent framebuffer for overlays. This is
- // probably not quite efficient on all GPUs, since we could filter out
- // opaque layers.
- clearWithColor(0.0, 0.0, 0.0, 0.0);
-
- setOutputDataSpace(display.outputDataspace);
- setDisplayMaxLuminance(display.maxLuminance);
- setDisplayColorTransform(display.colorTransform);
-
- const mat4 projectionMatrix =
- ui::Transform(display.orientation).asMatrix4() * mState.projectionMatrix;
-
- Mesh mesh = Mesh::Builder()
- .setPrimitive(Mesh::TRIANGLE_FAN)
- .setVertices(4 /* count */, 2 /* size */)
- .setTexCoords(2 /* size */)
- .setCropCoords(2 /* size */)
- .build();
- for (const auto& layer : layers) {
- if (blurLayers.size() > 0 && blurLayers.front() == layer) {
- blurLayers.pop_front();
-
- auto status = mBlurFilter->prepare();
- if (status != NO_ERROR) {
- ALOGE("Failed to render blur effect! Aborting GPU composition for buffer (%p).",
- buffer->getBuffer()->handle);
- checkErrors("Can't render first blur pass");
- resultPromise->set_value(base::unexpected(status));
- return;
- }
-
- if (blurLayers.size() == 0) {
- // Done blurring, time to bind the native FBO and render our blur onto it.
- fbo = std::make_unique<BindNativeBufferAsFramebuffer>(*this,
- buffer.get()
- ->getBuffer()
- ->getNativeBuffer(),
- useFramebufferCache);
- status = fbo->getStatus();
- setViewportAndProjection(display.physicalDisplay, display.clip);
- } else {
- // There's still something else to blur, so let's keep rendering to our FBO
- // instead of to the display.
- status = mBlurFilter->setAsDrawTarget(display,
- blurLayers.front().backgroundBlurRadius);
- }
- if (status != NO_ERROR) {
- ALOGE("Failed to bind framebuffer! Aborting GPU composition for buffer (%p).",
- buffer->getBuffer()->handle);
- checkErrors("Can't bind native framebuffer");
- resultPromise->set_value(base::unexpected(status));
- return;
- }
-
- status = mBlurFilter->render(blurLayersSize > 1);
- if (status != NO_ERROR) {
- ALOGE("Failed to render blur effect! Aborting GPU composition for buffer (%p).",
- buffer->getBuffer()->handle);
- checkErrors("Can't render blur filter");
- resultPromise->set_value(base::unexpected(status));
- return;
- }
- }
-
- // Ensure luminance is at least 100 nits to avoid div-by-zero
- const float maxLuminance = std::max(100.f, layer.source.buffer.maxLuminanceNits);
- mState.maxMasteringLuminance = maxLuminance;
- mState.maxContentLuminance = maxLuminance;
- mState.projectionMatrix = projectionMatrix * layer.geometry.positionTransform;
-
- const FloatRect bounds = layer.geometry.boundaries;
- Mesh::VertexArray<vec2> position(mesh.getPositionArray<vec2>());
- position[0] = vec2(bounds.left, bounds.top);
- position[1] = vec2(bounds.left, bounds.bottom);
- position[2] = vec2(bounds.right, bounds.bottom);
- position[3] = vec2(bounds.right, bounds.top);
-
- setupLayerCropping(layer, mesh);
- setColorTransform(layer.colorTransform);
-
- bool usePremultipliedAlpha = true;
- bool disableTexture = true;
- bool isOpaque = false;
- if (layer.source.buffer.buffer != nullptr) {
- disableTexture = false;
- isOpaque = layer.source.buffer.isOpaque;
-
- sp<GraphicBuffer> gBuf = layer.source.buffer.buffer->getBuffer();
- validateInputBufferUsage(gBuf);
- bindExternalTextureBuffer(layer.source.buffer.textureName, gBuf,
- layer.source.buffer.fence);
-
- usePremultipliedAlpha = layer.source.buffer.usePremultipliedAlpha;
- Texture texture(Texture::TEXTURE_EXTERNAL, layer.source.buffer.textureName);
- mat4 texMatrix = layer.source.buffer.textureTransform;
-
- texture.setMatrix(texMatrix.asArray());
- texture.setFiltering(layer.source.buffer.useTextureFiltering);
-
- texture.setDimensions(gBuf->getWidth(), gBuf->getHeight());
- setSourceY410BT2020(layer.source.buffer.isY410BT2020);
-
- renderengine::Mesh::VertexArray<vec2> texCoords(mesh.getTexCoordArray<vec2>());
- texCoords[0] = vec2(0.0, 0.0);
- texCoords[1] = vec2(0.0, 1.0);
- texCoords[2] = vec2(1.0, 1.0);
- texCoords[3] = vec2(1.0, 0.0);
- setupLayerTexturing(texture);
-
- // Do not cache protected EGLImage, protected memory is limited.
- if (gBuf->getUsage() & GRALLOC_USAGE_PROTECTED) {
- unmapExternalTextureBuffer(std::move(gBuf));
- }
- }
-
- const half3 solidColor = layer.source.solidColor;
- const half4 color = half4(solidColor.r, solidColor.g, solidColor.b, layer.alpha);
- const float radius =
- (layer.geometry.roundedCornersRadius.x + layer.geometry.roundedCornersRadius.y) /
- 2.0f;
- // Buffer sources will have a black solid color ignored in the shader,
- // so in that scenario the solid color passed here is arbitrary.
- setupLayerBlending(usePremultipliedAlpha, isOpaque, disableTexture, color, radius);
- if (layer.disableBlending) {
- glDisable(GL_BLEND);
- }
- setSourceDataSpace(layer.sourceDataspace);
-
- if (layer.shadow.length > 0.0f) {
- handleShadow(layer.geometry.boundaries, radius, layer.shadow);
- }
- // We only want to do a special handling for rounded corners when having rounded corners
- // is the only reason it needs to turn on blending, otherwise, we handle it like the
- // usual way since it needs to turn on blending anyway.
- else if (radius > 0.0 && color.a >= 1.0f && isOpaque) {
- handleRoundedCorners(display, layer, mesh);
- } else {
- drawMesh(mesh);
- }
-
- // Cleanup if there's a buffer source
- if (layer.source.buffer.buffer != nullptr) {
- disableBlending();
- setSourceY410BT2020(false);
- disableTexturing();
- }
- }
-
- base::unique_fd drawFence = flush();
-
- // If flush failed or we don't support native fences, we need to force the
- // gl command stream to be executed.
- if (drawFence.get() < 0) {
- bool success = finish();
- if (!success) {
- ALOGE("Failed to flush RenderEngine commands");
- checkErrors();
- // Chances are, something illegal happened (either the caller passed
- // us bad parameters, or we messed up our shader generation).
- resultPromise->set_value(base::unexpected(INVALID_OPERATION));
- return;
- }
- mLastDrawFence = nullptr;
- } else {
- // The caller takes ownership of drawFence, so we need to duplicate the
- // fd here.
- mLastDrawFence = new Fence(dup(drawFence.get()));
- }
- mPriorResourcesCleaned = false;
-
- checkErrors();
- resultPromise->set_value(sp<Fence>::make(std::move(drawFence)));
-}
-
-void GLESRenderEngine::setViewportAndProjection(Rect viewport, Rect clip) {
- ATRACE_CALL();
- mVpWidth = viewport.getWidth();
- mVpHeight = viewport.getHeight();
-
- // We pass the the top left corner instead of the bottom left corner,
- // because since we're rendering off-screen first.
- glViewport(viewport.left, viewport.top, mVpWidth, mVpHeight);
-
- mState.projectionMatrix = mat4::ortho(clip.left, clip.right, clip.top, clip.bottom, 0, 1);
-}
-
-void GLESRenderEngine::setupLayerBlending(bool premultipliedAlpha, bool opaque, bool disableTexture,
- const half4& color, float cornerRadius) {
- mState.isPremultipliedAlpha = premultipliedAlpha;
- mState.isOpaque = opaque;
- mState.color = color;
- mState.cornerRadius = cornerRadius;
-
- if (disableTexture) {
- mState.textureEnabled = false;
- }
-
- if (color.a < 1.0f || !opaque || cornerRadius > 0.0f) {
- glEnable(GL_BLEND);
- glBlendFuncSeparate(premultipliedAlpha ? GL_ONE : GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
- GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
- } else {
- glDisable(GL_BLEND);
- }
-}
-
-void GLESRenderEngine::setSourceY410BT2020(bool enable) {
- mState.isY410BT2020 = enable;
-}
-
-void GLESRenderEngine::setSourceDataSpace(Dataspace source) {
- mDataSpace = source;
-}
-
-void GLESRenderEngine::setOutputDataSpace(Dataspace dataspace) {
- mOutputDataSpace = dataspace;
-}
-
-void GLESRenderEngine::setDisplayMaxLuminance(const float maxLuminance) {
- mState.displayMaxLuminance = maxLuminance;
-}
-
-void GLESRenderEngine::setupLayerTexturing(const Texture& texture) {
- GLuint target = texture.getTextureTarget();
- glBindTexture(target, texture.getTextureName());
- GLenum filter = GL_NEAREST;
- if (texture.getFiltering()) {
- filter = GL_LINEAR;
- }
- glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filter);
- glTexParameteri(target, GL_TEXTURE_MIN_FILTER, filter);
-
- mState.texture = texture;
- mState.textureEnabled = true;
-}
-
-void GLESRenderEngine::setColorTransform(const mat4& colorTransform) {
- mState.colorMatrix = colorTransform;
-}
-
-void GLESRenderEngine::setDisplayColorTransform(const mat4& colorTransform) {
- mState.displayColorMatrix = colorTransform;
-}
-
-void GLESRenderEngine::disableTexturing() {
- mState.textureEnabled = false;
-}
-
-void GLESRenderEngine::disableBlending() {
- glDisable(GL_BLEND);
-}
-
-void GLESRenderEngine::setupFillWithColor(float r, float g, float b, float a) {
- mState.isPremultipliedAlpha = true;
- mState.isOpaque = false;
- mState.color = half4(r, g, b, a);
- mState.textureEnabled = false;
- glDisable(GL_BLEND);
-}
-
-void GLESRenderEngine::setupCornerRadiusCropSize(float width, float height) {
- mState.cropSize = half2(width, height);
-}
-
-void GLESRenderEngine::drawMesh(const Mesh& mesh) {
- ATRACE_CALL();
- if (mesh.getTexCoordsSize()) {
- glEnableVertexAttribArray(Program::texCoords);
- glVertexAttribPointer(Program::texCoords, mesh.getTexCoordsSize(), GL_FLOAT, GL_FALSE,
- mesh.getByteStride(), mesh.getTexCoords());
- }
-
- glVertexAttribPointer(Program::position, mesh.getVertexSize(), GL_FLOAT, GL_FALSE,
- mesh.getByteStride(), mesh.getPositions());
-
- if (mState.cornerRadius > 0.0f) {
- glEnableVertexAttribArray(Program::cropCoords);
- glVertexAttribPointer(Program::cropCoords, mesh.getVertexSize(), GL_FLOAT, GL_FALSE,
- mesh.getByteStride(), mesh.getCropCoords());
- }
-
- if (mState.drawShadows) {
- glEnableVertexAttribArray(Program::shadowColor);
- glVertexAttribPointer(Program::shadowColor, mesh.getShadowColorSize(), GL_FLOAT, GL_FALSE,
- mesh.getByteStride(), mesh.getShadowColor());
-
- glEnableVertexAttribArray(Program::shadowParams);
- glVertexAttribPointer(Program::shadowParams, mesh.getShadowParamsSize(), GL_FLOAT, GL_FALSE,
- mesh.getByteStride(), mesh.getShadowParams());
- }
-
- Description managedState = mState;
- // By default, DISPLAY_P3 is the only supported wide color output. However,
- // when HDR content is present, hardware composer may be able to handle
- // BT2020 data space, in that case, the output data space is set to be
- // BT2020_HLG or BT2020_PQ respectively. In GPU fall back we need
- // to respect this and convert non-HDR content to HDR format.
- if (mUseColorManagement) {
- Dataspace inputStandard = static_cast<Dataspace>(mDataSpace & Dataspace::STANDARD_MASK);
- Dataspace inputTransfer = static_cast<Dataspace>(mDataSpace & Dataspace::TRANSFER_MASK);
- Dataspace outputStandard =
- static_cast<Dataspace>(mOutputDataSpace & Dataspace::STANDARD_MASK);
- Dataspace outputTransfer =
- static_cast<Dataspace>(mOutputDataSpace & Dataspace::TRANSFER_MASK);
- bool needsXYZConversion = needsXYZTransformMatrix();
-
- // NOTE: if the input standard of the input dataspace is not STANDARD_DCI_P3 or
- // STANDARD_BT2020, it will be treated as STANDARD_BT709
- if (inputStandard != Dataspace::STANDARD_DCI_P3 &&
- inputStandard != Dataspace::STANDARD_BT2020) {
- inputStandard = Dataspace::STANDARD_BT709;
- }
-
- if (needsXYZConversion) {
- // The supported input color spaces are standard RGB, Display P3 and BT2020.
- switch (inputStandard) {
- case Dataspace::STANDARD_DCI_P3:
- managedState.inputTransformMatrix = mDisplayP3ToXyz;
- break;
- case Dataspace::STANDARD_BT2020:
- managedState.inputTransformMatrix = mBt2020ToXyz;
- break;
- default:
- managedState.inputTransformMatrix = mSrgbToXyz;
- break;
- }
-
- // The supported output color spaces are BT2020, Display P3 and standard RGB.
- switch (outputStandard) {
- case Dataspace::STANDARD_BT2020:
- managedState.outputTransformMatrix = mXyzToBt2020;
- break;
- case Dataspace::STANDARD_DCI_P3:
- managedState.outputTransformMatrix = mXyzToDisplayP3;
- break;
- default:
- managedState.outputTransformMatrix = mXyzToSrgb;
- break;
- }
- } else if (inputStandard != outputStandard) {
- // At this point, the input data space and output data space could be both
- // HDR data spaces, but they match each other, we do nothing in this case.
- // In addition to the case above, the input data space could be
- // - scRGB linear
- // - scRGB non-linear
- // - sRGB
- // - Display P3
- // - BT2020
- // The output data spaces could be
- // - sRGB
- // - Display P3
- // - BT2020
- switch (outputStandard) {
- case Dataspace::STANDARD_BT2020:
- if (inputStandard == Dataspace::STANDARD_BT709) {
- managedState.outputTransformMatrix = mSrgbToBt2020;
- } else if (inputStandard == Dataspace::STANDARD_DCI_P3) {
- managedState.outputTransformMatrix = mDisplayP3ToBt2020;
- }
- break;
- case Dataspace::STANDARD_DCI_P3:
- if (inputStandard == Dataspace::STANDARD_BT709) {
- managedState.outputTransformMatrix = mSrgbToDisplayP3;
- } else if (inputStandard == Dataspace::STANDARD_BT2020) {
- managedState.outputTransformMatrix = mBt2020ToDisplayP3;
- }
- break;
- default:
- if (inputStandard == Dataspace::STANDARD_DCI_P3) {
- managedState.outputTransformMatrix = mDisplayP3ToSrgb;
- } else if (inputStandard == Dataspace::STANDARD_BT2020) {
- managedState.outputTransformMatrix = mBt2020ToSrgb;
- }
- break;
- }
- }
-
- // we need to convert the RGB value to linear space and convert it back when:
- // - there is a color matrix that is not an identity matrix, or
- // - there is an output transform matrix that is not an identity matrix, or
- // - the input transfer function doesn't match the output transfer function.
- if (managedState.hasColorMatrix() || managedState.hasOutputTransformMatrix() ||
- inputTransfer != outputTransfer) {
- managedState.inputTransferFunction =
- Description::dataSpaceToTransferFunction(inputTransfer);
- managedState.outputTransferFunction =
- Description::dataSpaceToTransferFunction(outputTransfer);
- }
- }
-
- ProgramCache::getInstance().useProgram(mInProtectedContext ? mProtectedEGLContext : mEGLContext,
- managedState);
-
- if (mState.drawShadows) {
- glDrawElements(mesh.getPrimitive(), mesh.getIndexCount(), GL_UNSIGNED_SHORT,
- mesh.getIndices());
- } else {
- glDrawArrays(mesh.getPrimitive(), 0, mesh.getVertexCount());
- }
-
- if (mUseColorManagement && outputDebugPPMs) {
- static uint64_t managedColorFrameCount = 0;
- std::ostringstream out;
- out << "/data/texture_out" << managedColorFrameCount++;
- writePPM(out.str().c_str(), mVpWidth, mVpHeight);
- }
-
- if (mesh.getTexCoordsSize()) {
- glDisableVertexAttribArray(Program::texCoords);
- }
-
- if (mState.cornerRadius > 0.0f) {
- glDisableVertexAttribArray(Program::cropCoords);
- }
-
- if (mState.drawShadows) {
- glDisableVertexAttribArray(Program::shadowColor);
- glDisableVertexAttribArray(Program::shadowParams);
- }
-}
-
-size_t GLESRenderEngine::getMaxTextureSize() const {
- return mMaxTextureSize;
-}
-
-size_t GLESRenderEngine::getMaxViewportDims() const {
- return mMaxViewportDims[0] < mMaxViewportDims[1] ? mMaxViewportDims[0] : mMaxViewportDims[1];
-}
-
-void GLESRenderEngine::dump(std::string& result) {
- const GLExtensions& extensions = GLExtensions::getInstance();
- ProgramCache& cache = ProgramCache::getInstance();
-
- StringAppendF(&result, "EGL implementation : %s\n", extensions.getEGLVersion());
- StringAppendF(&result, "%s\n", extensions.getEGLExtensions());
- StringAppendF(&result, "GLES: %s, %s, %s\n", extensions.getVendor(), extensions.getRenderer(),
- extensions.getVersion());
- StringAppendF(&result, "%s\n", extensions.getExtensions());
- StringAppendF(&result, "RenderEngine supports protected context: %d\n",
- supportsProtectedContent());
- StringAppendF(&result, "RenderEngine is in protected context: %d\n", mInProtectedContext);
- StringAppendF(&result, "RenderEngine program cache size for unprotected context: %zu\n",
- cache.getSize(mEGLContext));
- StringAppendF(&result, "RenderEngine program cache size for protected context: %zu\n",
- cache.getSize(mProtectedEGLContext));
- StringAppendF(&result, "RenderEngine last dataspace conversion: (%s) to (%s)\n",
- dataspaceDetails(static_cast<android_dataspace>(mDataSpace)).c_str(),
- dataspaceDetails(static_cast<android_dataspace>(mOutputDataSpace)).c_str());
- {
- std::lock_guard<std::mutex> lock(mRenderingMutex);
- StringAppendF(&result, "RenderEngine image cache size: %zu\n", mImageCache.size());
- StringAppendF(&result, "Dumping buffer ids...\n");
- for (const auto& [id, unused] : mImageCache) {
- StringAppendF(&result, "0x%" PRIx64 "\n", id);
- }
- }
- {
- std::lock_guard<std::mutex> lock(mFramebufferImageCacheMutex);
- StringAppendF(&result, "RenderEngine framebuffer image cache size: %zu\n",
- mFramebufferImageCache.size());
- StringAppendF(&result, "Dumping buffer ids...\n");
- for (const auto& [id, unused] : mFramebufferImageCache) {
- StringAppendF(&result, "0x%" PRIx64 "\n", id);
- }
- }
-}
-
-GLESRenderEngine::GlesVersion GLESRenderEngine::parseGlesVersion(const char* str) {
- int major, minor;
- if (sscanf(str, "OpenGL ES-CM %d.%d", &major, &minor) != 2) {
- if (sscanf(str, "OpenGL ES %d.%d", &major, &minor) != 2) {
- ALOGW("Unable to parse GL_VERSION string: \"%s\"", str);
- return GLES_VERSION_1_0;
- }
- }
-
- if (major == 1 && minor == 0) return GLES_VERSION_1_0;
- if (major == 1 && minor >= 1) return GLES_VERSION_1_1;
- if (major == 2 && minor >= 0) return GLES_VERSION_2_0;
- if (major == 3 && minor >= 0) return GLES_VERSION_3_0;
-
- ALOGW("Unrecognized OpenGL ES version: %d.%d", major, minor);
- return GLES_VERSION_1_0;
-}
-
-EGLContext GLESRenderEngine::createEglContext(EGLDisplay display, EGLConfig config,
- EGLContext shareContext,
- std::optional<ContextPriority> contextPriority,
- Protection protection) {
- EGLint renderableType = 0;
- if (config == EGL_NO_CONFIG) {
- renderableType = EGL_OPENGL_ES3_BIT;
- } else if (!eglGetConfigAttrib(display, config, EGL_RENDERABLE_TYPE, &renderableType)) {
- LOG_ALWAYS_FATAL("can't query EGLConfig RENDERABLE_TYPE");
- }
- EGLint contextClientVersion = 0;
- if (renderableType & EGL_OPENGL_ES3_BIT) {
- contextClientVersion = 3;
- } else if (renderableType & EGL_OPENGL_ES2_BIT) {
- contextClientVersion = 2;
- } else if (renderableType & EGL_OPENGL_ES_BIT) {
- contextClientVersion = 1;
- } else {
- LOG_ALWAYS_FATAL("no supported EGL_RENDERABLE_TYPEs");
- }
-
- std::vector<EGLint> contextAttributes;
- contextAttributes.reserve(7);
- contextAttributes.push_back(EGL_CONTEXT_CLIENT_VERSION);
- contextAttributes.push_back(contextClientVersion);
- if (contextPriority) {
- contextAttributes.push_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG);
- switch (*contextPriority) {
- case ContextPriority::REALTIME:
- contextAttributes.push_back(EGL_CONTEXT_PRIORITY_REALTIME_NV);
- break;
- case ContextPriority::MEDIUM:
- contextAttributes.push_back(EGL_CONTEXT_PRIORITY_MEDIUM_IMG);
- break;
- case ContextPriority::LOW:
- contextAttributes.push_back(EGL_CONTEXT_PRIORITY_LOW_IMG);
- break;
- case ContextPriority::HIGH:
- default:
- contextAttributes.push_back(EGL_CONTEXT_PRIORITY_HIGH_IMG);
- break;
- }
- }
- if (protection == Protection::PROTECTED) {
- contextAttributes.push_back(EGL_PROTECTED_CONTENT_EXT);
- contextAttributes.push_back(EGL_TRUE);
- }
- contextAttributes.push_back(EGL_NONE);
-
- EGLContext context = eglCreateContext(display, config, shareContext, contextAttributes.data());
-
- if (contextClientVersion == 3 && context == EGL_NO_CONTEXT) {
- // eglGetConfigAttrib indicated we can create GLES 3 context, but we failed, thus
- // EGL_NO_CONTEXT so that we can abort.
- if (config != EGL_NO_CONFIG) {
- return context;
- }
- // If |config| is EGL_NO_CONFIG, we speculatively try to create GLES 3 context, so we should
- // try to fall back to GLES 2.
- contextAttributes[1] = 2;
- context = eglCreateContext(display, config, shareContext, contextAttributes.data());
- }
-
- return context;
-}
-
-EGLSurface GLESRenderEngine::createStubEglPbufferSurface(EGLDisplay display, EGLConfig config,
- int hwcFormat, Protection protection) {
- EGLConfig stubConfig = config;
- if (stubConfig == EGL_NO_CONFIG) {
- stubConfig = chooseEglConfig(display, hwcFormat, /*logConfig*/ true);
- }
- std::vector<EGLint> attributes;
- attributes.reserve(7);
- attributes.push_back(EGL_WIDTH);
- attributes.push_back(1);
- attributes.push_back(EGL_HEIGHT);
- attributes.push_back(1);
- if (protection == Protection::PROTECTED) {
- attributes.push_back(EGL_PROTECTED_CONTENT_EXT);
- attributes.push_back(EGL_TRUE);
- }
- attributes.push_back(EGL_NONE);
-
- return eglCreatePbufferSurface(display, stubConfig, attributes.data());
-}
-
-bool GLESRenderEngine::isHdrDataSpace(const Dataspace dataSpace) const {
- const Dataspace standard = static_cast<Dataspace>(dataSpace & Dataspace::STANDARD_MASK);
- const Dataspace transfer = static_cast<Dataspace>(dataSpace & Dataspace::TRANSFER_MASK);
- return standard == Dataspace::STANDARD_BT2020 &&
- (transfer == Dataspace::TRANSFER_ST2084 || transfer == Dataspace::TRANSFER_HLG);
-}
-
-// For convenience, we want to convert the input color space to XYZ color space first,
-// and then convert from XYZ color space to output color space when
-// - SDR and HDR contents are mixed, either SDR content will be converted to HDR or
-// HDR content will be tone-mapped to SDR; Or,
-// - there are HDR PQ and HLG contents presented at the same time, where we want to convert
-// HLG content to PQ content.
-// In either case above, we need to operate the Y value in XYZ color space. Thus, when either
-// input data space or output data space is HDR data space, and the input transfer function
-// doesn't match the output transfer function, we would enable an intermediate transfrom to
-// XYZ color space.
-bool GLESRenderEngine::needsXYZTransformMatrix() const {
- const bool isInputHdrDataSpace = isHdrDataSpace(mDataSpace);
- const bool isOutputHdrDataSpace = isHdrDataSpace(mOutputDataSpace);
- const Dataspace inputTransfer = static_cast<Dataspace>(mDataSpace & Dataspace::TRANSFER_MASK);
- const Dataspace outputTransfer =
- static_cast<Dataspace>(mOutputDataSpace & Dataspace::TRANSFER_MASK);
-
- return (isInputHdrDataSpace || isOutputHdrDataSpace) && inputTransfer != outputTransfer;
-}
-
-bool GLESRenderEngine::isImageCachedForTesting(uint64_t bufferId) {
- std::lock_guard<std::mutex> lock(mRenderingMutex);
- const auto& cachedImage = mImageCache.find(bufferId);
- return cachedImage != mImageCache.end();
-}
-
-bool GLESRenderEngine::isTextureNameKnownForTesting(uint32_t texName) {
- const auto& entry = mTextureView.find(texName);
- return entry != mTextureView.end();
-}
-
-std::optional<uint64_t> GLESRenderEngine::getBufferIdForTextureNameForTesting(uint32_t texName) {
- const auto& entry = mTextureView.find(texName);
- return entry != mTextureView.end() ? entry->second : std::nullopt;
-}
-
-bool GLESRenderEngine::isFramebufferImageCachedForTesting(uint64_t bufferId) {
- std::lock_guard<std::mutex> lock(mFramebufferImageCacheMutex);
- return std::any_of(mFramebufferImageCache.cbegin(), mFramebufferImageCache.cend(),
- [=](std::pair<uint64_t, EGLImageKHR> image) {
- return image.first == bufferId;
- });
-}
-
-// FlushTracer implementation
-GLESRenderEngine::FlushTracer::FlushTracer(GLESRenderEngine* engine) : mEngine(engine) {
- mThread = std::thread(&GLESRenderEngine::FlushTracer::loop, this);
-}
-
-GLESRenderEngine::FlushTracer::~FlushTracer() {
- {
- std::lock_guard<std::mutex> lock(mMutex);
- mRunning = false;
- }
- mCondition.notify_all();
- if (mThread.joinable()) {
- mThread.join();
- }
-}
-
-void GLESRenderEngine::FlushTracer::queueSync(EGLSyncKHR sync) {
- std::lock_guard<std::mutex> lock(mMutex);
- char name[64];
- const uint64_t frameNum = mFramesQueued++;
- snprintf(name, sizeof(name), "Queueing sync for frame: %lu",
- static_cast<unsigned long>(frameNum));
- ATRACE_NAME(name);
- mQueue.push({sync, frameNum});
- ATRACE_INT("GPU Frames Outstanding", mQueue.size());
- mCondition.notify_one();
-}
-
-void GLESRenderEngine::FlushTracer::loop() {
- while (mRunning) {
- QueueEntry entry;
- {
- std::lock_guard<std::mutex> lock(mMutex);
-
- mCondition.wait(mMutex,
- [&]() REQUIRES(mMutex) { return !mQueue.empty() || !mRunning; });
-
- if (!mRunning) {
- // if mRunning is false, then FlushTracer is being destroyed, so
- // bail out now.
- break;
- }
- entry = mQueue.front();
- mQueue.pop();
- }
- {
- char name[64];
- snprintf(name, sizeof(name), "waiting for frame %lu",
- static_cast<unsigned long>(entry.mFrameNum));
- ATRACE_NAME(name);
- mEngine->waitSync(entry.mSync, 0);
- }
- }
-}
-
-void GLESRenderEngine::handleShadow(const FloatRect& casterRect, float casterCornerRadius,
- const ShadowSettings& settings) {
- ATRACE_CALL();
- const float casterZ = settings.length / 2.0f;
- const GLShadowVertexGenerator shadows(casterRect, casterCornerRadius, casterZ,
- settings.casterIsTranslucent, settings.ambientColor,
- settings.spotColor, settings.lightPos,
- settings.lightRadius);
-
- // setup mesh for both shadows
- Mesh mesh = Mesh::Builder()
- .setPrimitive(Mesh::TRIANGLES)
- .setVertices(shadows.getVertexCount(), 2 /* size */)
- .setShadowAttrs()
- .setIndices(shadows.getIndexCount())
- .build();
-
- Mesh::VertexArray<vec2> position = mesh.getPositionArray<vec2>();
- Mesh::VertexArray<vec4> shadowColor = mesh.getShadowColorArray<vec4>();
- Mesh::VertexArray<vec3> shadowParams = mesh.getShadowParamsArray<vec3>();
- shadows.fillVertices(position, shadowColor, shadowParams);
- shadows.fillIndices(mesh.getIndicesArray());
-
- mState.cornerRadius = 0.0f;
- mState.drawShadows = true;
- setupLayerTexturing(mShadowTexture->getTexture());
- drawMesh(mesh);
- mState.drawShadows = false;
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLESRenderEngine.h b/libs/renderengine/gl/GLESRenderEngine.h
deleted file mode 100644
index 402ff529d7..0000000000
--- a/libs/renderengine/gl/GLESRenderEngine.h
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * Copyright 2013 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.
- */
-
-#ifndef SF_GLESRENDERENGINE_H_
-#define SF_GLESRENDERENGINE_H_
-
-#include <condition_variable>
-#include <deque>
-#include <mutex>
-#include <queue>
-#include <thread>
-#include <unordered_map>
-
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <GLES2/gl2.h>
-#include <android-base/thread_annotations.h>
-#include <renderengine/RenderEngine.h>
-#include <renderengine/private/Description.h>
-#include <sys/types.h>
-#include <ui/FenceResult.h>
-#include "GLShadowTexture.h"
-#include "ImageManager.h"
-
-#define EGL_NO_CONFIG ((EGLConfig)0)
-
-namespace android {
-
-namespace renderengine {
-
-class Mesh;
-class Texture;
-
-namespace gl {
-
-class GLImage;
-class BlurFilter;
-
-class GLESRenderEngine : public RenderEngine {
-public:
- static std::unique_ptr<GLESRenderEngine> create(const RenderEngineCreationArgs& args);
-
- GLESRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display, EGLConfig config,
- EGLContext ctxt, EGLSurface stub, EGLContext protectedContext,
- EGLSurface protectedStub);
- ~GLESRenderEngine() override EXCLUDES(mRenderingMutex);
-
- std::future<void> primeCache() override;
- void genTextures(size_t count, uint32_t* names) override;
- void deleteTextures(size_t count, uint32_t const* names) override;
- bool isProtected() const { return mInProtectedContext; }
- bool supportsProtectedContent() const override;
- void useProtectedContext(bool useProtectedContext) override;
- void cleanupPostRender() override;
- int getContextPriority() override;
- bool supportsBackgroundBlur() override { return mBlurFilter != nullptr; }
- void onActiveDisplaySizeChanged(ui::Size size) override {}
-
- EGLDisplay getEGLDisplay() const { return mEGLDisplay; }
- // Creates an output image for rendering to
- EGLImageKHR createFramebufferImageIfNeeded(ANativeWindowBuffer* nativeBuffer, bool isProtected,
- bool useFramebufferCache)
- EXCLUDES(mFramebufferImageCacheMutex);
-
- // Test-only methods
- // Returns true iff mImageCache contains an image keyed by bufferId
- bool isImageCachedForTesting(uint64_t bufferId) EXCLUDES(mRenderingMutex);
- // Returns true iff texName was previously generated by RenderEngine and was
- // not destroyed.
- bool isTextureNameKnownForTesting(uint32_t texName);
- // Returns the buffer ID of the content bound to texName, or nullopt if no
- // such mapping exists.
- std::optional<uint64_t> getBufferIdForTextureNameForTesting(uint32_t texName);
- // Returns true iff mFramebufferImageCache contains an image keyed by bufferId
- bool isFramebufferImageCachedForTesting(uint64_t bufferId)
- EXCLUDES(mFramebufferImageCacheMutex);
- // These are wrappers around public methods above, but exposing Barrier
- // objects so that tests can block.
- std::shared_ptr<ImageManager::Barrier> cacheExternalTextureBufferForTesting(
- const sp<GraphicBuffer>& buffer);
- std::shared_ptr<ImageManager::Barrier> unbindExternalTextureBufferForTesting(uint64_t bufferId);
-
-protected:
- Framebuffer* getFramebufferForDrawing();
- void dump(std::string& result) override EXCLUDES(mRenderingMutex)
- EXCLUDES(mFramebufferImageCacheMutex);
- size_t getMaxTextureSize() const override;
- size_t getMaxViewportDims() const override;
- void mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer, bool isRenderable)
- EXCLUDES(mRenderingMutex);
- void unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) EXCLUDES(mRenderingMutex);
- bool canSkipPostRenderCleanup() const override;
- void drawLayersInternal(const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
- const DisplaySettings& display,
- const std::vector<LayerSettings>& layers,
- const std::shared_ptr<ExternalTexture>& buffer,
- const bool useFramebufferCache, base::unique_fd&& bufferFence) override;
-
-private:
- friend class BindNativeBufferAsFramebuffer;
-
- enum GlesVersion {
- GLES_VERSION_1_0 = 0x10000,
- GLES_VERSION_1_1 = 0x10001,
- GLES_VERSION_2_0 = 0x20000,
- GLES_VERSION_3_0 = 0x30000,
- };
-
- static EGLConfig chooseEglConfig(EGLDisplay display, int format, bool logConfig);
- static GlesVersion parseGlesVersion(const char* str);
- static EGLContext createEglContext(EGLDisplay display, EGLConfig config,
- EGLContext shareContext,
- std::optional<ContextPriority> contextPriority,
- Protection protection);
- static std::optional<RenderEngine::ContextPriority> createContextPriority(
- const RenderEngineCreationArgs& args);
- static EGLSurface createStubEglPbufferSurface(EGLDisplay display, EGLConfig config,
- int hwcFormat, Protection protection);
- std::unique_ptr<Framebuffer> createFramebuffer();
- std::unique_ptr<Image> createImage();
- void checkErrors() const;
- void checkErrors(const char* tag) const;
- void setScissor(const Rect& region);
- void disableScissor();
- bool waitSync(EGLSyncKHR sync, EGLint flags);
- status_t cacheExternalTextureBufferInternal(const sp<GraphicBuffer>& buffer)
- EXCLUDES(mRenderingMutex);
- void unbindExternalTextureBufferInternal(uint64_t bufferId) EXCLUDES(mRenderingMutex);
- status_t bindFrameBuffer(Framebuffer* framebuffer);
- void unbindFrameBuffer(Framebuffer* framebuffer);
- void bindExternalTextureImage(uint32_t texName, const Image& image);
- void bindExternalTextureBuffer(uint32_t texName, const sp<GraphicBuffer>& buffer,
- const sp<Fence>& fence) EXCLUDES(mRenderingMutex);
- void cleanFramebufferCache() EXCLUDES(mFramebufferImageCacheMutex) override;
-
- // A data space is considered HDR data space if it has BT2020 color space
- // with PQ or HLG transfer function.
- bool isHdrDataSpace(const ui::Dataspace dataSpace) const;
- bool needsXYZTransformMatrix() const;
- // Defines the viewport, and sets the projection matrix to the projection
- // defined by the clip.
- void setViewportAndProjection(Rect viewport, Rect clip);
- // Evicts stale images from the buffer cache.
- void evictImages(const std::vector<LayerSettings>& layers);
- // Computes the cropping window for the layer and sets up cropping
- // coordinates for the mesh.
- FloatRect setupLayerCropping(const LayerSettings& layer, Mesh& mesh);
-
- // We do a special handling for rounded corners when it's possible to turn off blending
- // for the majority of the layer. The rounded corners needs to turn on blending such that
- // we can set the alpha value correctly, however, only the corners need this, and since
- // blending is an expensive operation, we want to turn off blending when it's not necessary.
- void handleRoundedCorners(const DisplaySettings& display, const LayerSettings& layer,
- const Mesh& mesh);
- base::unique_fd flush();
- bool finish();
- bool waitFence(base::unique_fd fenceFd);
- void clearWithColor(float red, float green, float blue, float alpha);
- void fillRegionWithColor(const Region& region, float red, float green, float blue, float alpha);
- void handleShadow(const FloatRect& casterRect, float casterCornerRadius,
- const ShadowSettings& shadowSettings);
- void setupLayerBlending(bool premultipliedAlpha, bool opaque, bool disableTexture,
- const half4& color, float cornerRadius);
- void setupLayerTexturing(const Texture& texture);
- void setupFillWithColor(float r, float g, float b, float a);
- void setColorTransform(const mat4& colorTransform);
- void setDisplayColorTransform(const mat4& colorTransform);
- void disableTexturing();
- void disableBlending();
- void setupCornerRadiusCropSize(float width, float height);
-
- // HDR and color management related functions and state
- void setSourceY410BT2020(bool enable);
- void setSourceDataSpace(ui::Dataspace source);
- void setOutputDataSpace(ui::Dataspace dataspace);
- void setDisplayMaxLuminance(const float maxLuminance);
-
- // drawing
- void drawMesh(const Mesh& mesh);
-
- EGLDisplay mEGLDisplay;
- EGLConfig mEGLConfig;
- EGLContext mEGLContext;
- EGLSurface mStubSurface;
- EGLContext mProtectedEGLContext;
- EGLSurface mProtectedStubSurface;
- GLint mMaxViewportDims[2];
- GLint mMaxTextureSize;
- GLuint mVpWidth;
- GLuint mVpHeight;
- Description mState;
- std::unique_ptr<GLShadowTexture> mShadowTexture = nullptr;
-
- mat4 mSrgbToXyz;
- mat4 mDisplayP3ToXyz;
- mat4 mBt2020ToXyz;
- mat4 mXyzToSrgb;
- mat4 mXyzToDisplayP3;
- mat4 mXyzToBt2020;
- mat4 mSrgbToDisplayP3;
- mat4 mSrgbToBt2020;
- mat4 mDisplayP3ToSrgb;
- mat4 mDisplayP3ToBt2020;
- mat4 mBt2020ToSrgb;
- mat4 mBt2020ToDisplayP3;
-
- bool mInProtectedContext = false;
- // If set to true, then enables tracing flush() and finish() to systrace.
- bool mTraceGpuCompletion = false;
- // Maximum size of mFramebufferImageCache. If more images would be cached, then (approximately)
- // the last recently used buffer should be kicked out.
- uint32_t mFramebufferImageCacheSize = 0;
-
- // Cache of output images, keyed by corresponding GraphicBuffer ID.
- std::deque<std::pair<uint64_t, EGLImageKHR>> mFramebufferImageCache
- GUARDED_BY(mFramebufferImageCacheMutex);
- // The only reason why we have this mutex is so that we don't segfault when
- // dumping info.
- std::mutex mFramebufferImageCacheMutex;
-
- // Current dataspace of layer being rendered
- ui::Dataspace mDataSpace = ui::Dataspace::UNKNOWN;
-
- // Current output dataspace of the render engine
- ui::Dataspace mOutputDataSpace = ui::Dataspace::UNKNOWN;
-
- // Whether device supports color management, currently color management
- // supports sRGB, DisplayP3 color spaces.
- const bool mUseColorManagement = false;
-
- // Whether only shaders performing tone mapping from HDR to SDR will be generated on
- // primeCache().
- const bool mPrecacheToneMapperShaderOnly = false;
-
- // Cache of GL images that we'll store per GraphicBuffer ID
- std::unordered_map<uint64_t, std::unique_ptr<Image>> mImageCache GUARDED_BY(mRenderingMutex);
- std::unordered_map<uint32_t, std::optional<uint64_t>> mTextureView;
-
- // Mutex guarding rendering operations, so that:
- // 1. GL operations aren't interleaved, and
- // 2. Internal state related to rendering that is potentially modified by
- // multiple threads is guaranteed thread-safe.
- std::mutex mRenderingMutex;
-
- std::unique_ptr<Framebuffer> mDrawingBuffer;
- // this is a 1x1 RGB buffer, but over-allocate in case a driver wants more
- // memory or if it needs to satisfy alignment requirements. In this case:
- // assume that each channel requires 4 bytes, and add 3 additional bytes to
- // ensure that we align on a word. Allocating 16 bytes will provide a
- // guarantee that we don't clobber memory.
- uint32_t mPlaceholderDrawBuffer[4];
- // Placeholder buffer and image, similar to mPlaceholderDrawBuffer, but
- // instead these are intended for cleaning up texture memory with the
- // GL_TEXTURE_EXTERNAL_OES target.
- ANativeWindowBuffer* mPlaceholderBuffer = nullptr;
- EGLImage mPlaceholderImage = EGL_NO_IMAGE_KHR;
- sp<Fence> mLastDrawFence;
- // Store a separate boolean checking if prior resources were cleaned up, as
- // devices that don't support native sync fences can't rely on a last draw
- // fence that doesn't exist.
- bool mPriorResourcesCleaned = true;
-
- // Blur effect processor, only instantiated when a layer requests it.
- BlurFilter* mBlurFilter = nullptr;
-
- class FlushTracer {
- public:
- FlushTracer(GLESRenderEngine* engine);
- ~FlushTracer();
- void queueSync(EGLSyncKHR sync) EXCLUDES(mMutex);
-
- struct QueueEntry {
- EGLSyncKHR mSync = nullptr;
- uint64_t mFrameNum = 0;
- };
-
- private:
- void loop();
- GLESRenderEngine* const mEngine;
- std::thread mThread;
- std::condition_variable_any mCondition;
- std::mutex mMutex;
- std::queue<QueueEntry> mQueue GUARDED_BY(mMutex);
- uint64_t mFramesQueued GUARDED_BY(mMutex) = 0;
- bool mRunning = true;
- };
- friend class FlushTracer;
- friend class ImageManager;
- friend class GLFramebuffer;
- friend class BlurFilter;
- friend class GenericProgram;
- std::unique_ptr<FlushTracer> mFlushTracer;
- std::unique_ptr<ImageManager> mImageManager;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
-
-#endif /* SF_GLESRENDERENGINE_H_ */
diff --git a/libs/renderengine/gl/GLFramebuffer.cpp b/libs/renderengine/gl/GLFramebuffer.cpp
deleted file mode 100644
index 58d6caa48a..0000000000
--- a/libs/renderengine/gl/GLFramebuffer.cpp
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * 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 ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "GLFramebuffer.h"
-
-#include <GLES/gl.h>
-#include <GLES/glext.h>
-#include <GLES2/gl2ext.h>
-#include <GLES3/gl3.h>
-#include <gui/DebugEGLImageTracker.h>
-#include <nativebase/nativebase.h>
-#include <utils/Trace.h>
-#include "GLESRenderEngine.h"
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-GLFramebuffer::GLFramebuffer(GLESRenderEngine& engine)
- : mEngine(engine), mEGLDisplay(engine.getEGLDisplay()), mEGLImage(EGL_NO_IMAGE_KHR) {
- glGenTextures(1, &mTextureName);
- glGenFramebuffers(1, &mFramebufferName);
-}
-
-GLFramebuffer::~GLFramebuffer() {
- setNativeWindowBuffer(nullptr, false, false);
- glDeleteFramebuffers(1, &mFramebufferName);
- glDeleteTextures(1, &mTextureName);
-}
-
-bool GLFramebuffer::setNativeWindowBuffer(ANativeWindowBuffer* nativeBuffer, bool isProtected,
- const bool useFramebufferCache) {
- ATRACE_CALL();
- if (mEGLImage != EGL_NO_IMAGE_KHR) {
- if (!usingFramebufferCache) {
- eglDestroyImageKHR(mEGLDisplay, mEGLImage);
- DEBUG_EGL_IMAGE_TRACKER_DESTROY();
- }
- mEGLImage = EGL_NO_IMAGE_KHR;
- mBufferWidth = 0;
- mBufferHeight = 0;
- }
-
- if (nativeBuffer) {
- mEGLImage = mEngine.createFramebufferImageIfNeeded(nativeBuffer, isProtected,
- useFramebufferCache);
- if (mEGLImage == EGL_NO_IMAGE_KHR) {
- return false;
- }
- usingFramebufferCache = useFramebufferCache;
- mBufferWidth = nativeBuffer->width;
- mBufferHeight = nativeBuffer->height;
- }
- return true;
-}
-
-void GLFramebuffer::allocateBuffers(uint32_t width, uint32_t height, void* data) {
- ATRACE_CALL();
-
- glBindTexture(GL_TEXTURE_2D, mTextureName);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
-
- mBufferHeight = height;
- mBufferWidth = width;
- mEngine.checkErrors("Allocating Fbo texture");
-
- bind();
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextureName, 0);
- mStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
- unbind();
- glBindTexture(GL_TEXTURE_2D, 0);
-
- if (mStatus != GL_FRAMEBUFFER_COMPLETE) {
- ALOGE("Frame buffer is not complete. Error %d", mStatus);
- }
-}
-
-void GLFramebuffer::bind() const {
- glBindFramebuffer(GL_FRAMEBUFFER, mFramebufferName);
-}
-
-void GLFramebuffer::bindAsReadBuffer() const {
- glBindFramebuffer(GL_READ_FRAMEBUFFER, mFramebufferName);
-}
-
-void GLFramebuffer::bindAsDrawBuffer() const {
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mFramebufferName);
-}
-
-void GLFramebuffer::unbind() const {
- glBindFramebuffer(GL_FRAMEBUFFER, 0);
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLFramebuffer.h b/libs/renderengine/gl/GLFramebuffer.h
deleted file mode 100644
index 6757695ddb..0000000000
--- a/libs/renderengine/gl/GLFramebuffer.h
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.
- */
-
-#pragma once
-
-#include <cstdint>
-
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <GLES2/gl2.h>
-#include <renderengine/Framebuffer.h>
-
-struct ANativeWindowBuffer;
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-class GLESRenderEngine;
-
-class GLFramebuffer : public renderengine::Framebuffer {
-public:
- explicit GLFramebuffer(GLESRenderEngine& engine);
- explicit GLFramebuffer(GLESRenderEngine& engine, bool multiTarget);
- ~GLFramebuffer() override;
-
- bool setNativeWindowBuffer(ANativeWindowBuffer* nativeBuffer, bool isProtected,
- const bool useFramebufferCache) override;
- void allocateBuffers(uint32_t width, uint32_t height, void* data = nullptr);
- EGLImageKHR getEGLImage() const { return mEGLImage; }
- uint32_t getTextureName() const { return mTextureName; }
- uint32_t getFramebufferName() const { return mFramebufferName; }
- int32_t getBufferHeight() const { return mBufferHeight; }
- int32_t getBufferWidth() const { return mBufferWidth; }
- GLenum getStatus() const { return mStatus; }
- void bind() const;
- void bindAsReadBuffer() const;
- void bindAsDrawBuffer() const;
- void unbind() const;
-
-private:
- GLESRenderEngine& mEngine;
- EGLDisplay mEGLDisplay;
- EGLImageKHR mEGLImage;
- bool usingFramebufferCache = false;
- GLenum mStatus = GL_FRAMEBUFFER_UNSUPPORTED;
- uint32_t mTextureName, mFramebufferName;
-
- int32_t mBufferHeight = 0;
- int32_t mBufferWidth = 0;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLImage.cpp b/libs/renderengine/gl/GLImage.cpp
deleted file mode 100644
index 8497721956..0000000000
--- a/libs/renderengine/gl/GLImage.cpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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 ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "GLImage.h"
-
-#include <vector>
-
-#include <gui/DebugEGLImageTracker.h>
-#include <log/log.h>
-#include <utils/Trace.h>
-#include "GLESRenderEngine.h"
-#include "GLExtensions.h"
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-static std::vector<EGLint> buildAttributeList(bool isProtected) {
- std::vector<EGLint> attrs;
- attrs.reserve(16);
-
- attrs.push_back(EGL_IMAGE_PRESERVED_KHR);
- attrs.push_back(EGL_TRUE);
-
- if (isProtected && GLExtensions::getInstance().hasProtectedContent()) {
- attrs.push_back(EGL_PROTECTED_CONTENT_EXT);
- attrs.push_back(EGL_TRUE);
- }
-
- attrs.push_back(EGL_NONE);
-
- return attrs;
-}
-
-GLImage::GLImage(const GLESRenderEngine& engine) : mEGLDisplay(engine.getEGLDisplay()) {}
-
-GLImage::~GLImage() {
- setNativeWindowBuffer(nullptr, false);
-}
-
-bool GLImage::setNativeWindowBuffer(ANativeWindowBuffer* buffer, bool isProtected) {
- ATRACE_CALL();
- if (mEGLImage != EGL_NO_IMAGE_KHR) {
- if (!eglDestroyImageKHR(mEGLDisplay, mEGLImage)) {
- ALOGE("failed to destroy image: %#x", eglGetError());
- }
- DEBUG_EGL_IMAGE_TRACKER_DESTROY();
- mEGLImage = EGL_NO_IMAGE_KHR;
- }
-
- if (buffer) {
- std::vector<EGLint> attrs = buildAttributeList(isProtected);
- mEGLImage = eglCreateImageKHR(mEGLDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
- static_cast<EGLClientBuffer>(buffer), attrs.data());
- if (mEGLImage == EGL_NO_IMAGE_KHR) {
- ALOGE("failed to create EGLImage: %#x", eglGetError());
- return false;
- }
- DEBUG_EGL_IMAGE_TRACKER_CREATE();
- mProtected = isProtected;
- }
-
- return true;
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLImage.h b/libs/renderengine/gl/GLImage.h
deleted file mode 100644
index 59d6ce3549..0000000000
--- a/libs/renderengine/gl/GLImage.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.
- */
-
-#pragma once
-
-#include <cstdint>
-
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <android-base/macros.h>
-#include <renderengine/Image.h>
-
-struct ANativeWindowBuffer;
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-class GLESRenderEngine;
-
-class GLImage : public renderengine::Image {
-public:
- explicit GLImage(const GLESRenderEngine& engine);
- ~GLImage() override;
-
- bool setNativeWindowBuffer(ANativeWindowBuffer* buffer, bool isProtected) override;
-
- EGLImageKHR getEGLImage() const { return mEGLImage; }
- bool isProtected() const { return mProtected; }
-
-private:
- EGLDisplay mEGLDisplay;
- EGLImageKHR mEGLImage = EGL_NO_IMAGE_KHR;
- bool mProtected = false;
-
- DISALLOW_COPY_AND_ASSIGN(GLImage);
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLShadowTexture.cpp b/libs/renderengine/gl/GLShadowTexture.cpp
deleted file mode 100644
index 2423a3467e..0000000000
--- a/libs/renderengine/gl/GLShadowTexture.cpp
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2020 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.
- */
-
-#include <GLES/gl.h>
-#include <GLES/glext.h>
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-#include <GLES3/gl3.h>
-
-#include "GLShadowTexture.h"
-#include "GLSkiaShadowPort.h"
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-GLShadowTexture::GLShadowTexture() {
- fillShadowTextureData(mTextureData, SHADOW_TEXTURE_WIDTH);
-
- glGenTextures(1, &mName);
- glBindTexture(GL_TEXTURE_2D, mName);
- glTexImage2D(GL_TEXTURE_2D, 0 /* base image level */, GL_ALPHA, SHADOW_TEXTURE_WIDTH,
- SHADOW_TEXTURE_HEIGHT, 0 /* border */, GL_ALPHA, GL_UNSIGNED_BYTE, mTextureData);
- mTexture.init(Texture::TEXTURE_2D, mName);
- mTexture.setFiltering(true);
- mTexture.setDimensions(SHADOW_TEXTURE_WIDTH, 1);
-}
-
-GLShadowTexture::~GLShadowTexture() {
- glDeleteTextures(1, &mName);
-}
-
-const Texture& GLShadowTexture::getTexture() {
- return mTexture;
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLShadowTexture.h b/libs/renderengine/gl/GLShadowTexture.h
deleted file mode 100644
index 250a9d77d0..0000000000
--- a/libs/renderengine/gl/GLShadowTexture.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2020 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.
- */
-
-#pragma once
-
-#include <renderengine/Texture.h>
-#include <cstdint>
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-class GLShadowTexture {
-public:
- GLShadowTexture();
- ~GLShadowTexture();
-
- const Texture& getTexture();
-
-private:
- static constexpr int SHADOW_TEXTURE_WIDTH = 128;
- static constexpr int SHADOW_TEXTURE_HEIGHT = 1;
-
- GLuint mName;
- Texture mTexture;
- uint8_t mTextureData[SHADOW_TEXTURE_WIDTH];
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLShadowVertexGenerator.cpp b/libs/renderengine/gl/GLShadowVertexGenerator.cpp
deleted file mode 100644
index 3181f9bebb..0000000000
--- a/libs/renderengine/gl/GLShadowVertexGenerator.cpp
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2019 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.
- */
-
-#include <renderengine/Mesh.h>
-
-#include <math/vec4.h>
-
-#include <ui/Rect.h>
-#include <ui/Transform.h>
-
-#include "GLShadowVertexGenerator.h"
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-GLShadowVertexGenerator::GLShadowVertexGenerator(const FloatRect& casterRect,
- float casterCornerRadius, float casterZ,
- bool casterIsTranslucent, const vec4& ambientColor,
- const vec4& spotColor, const vec3& lightPosition,
- float lightRadius) {
- mDrawAmbientShadow = ambientColor.a > 0.f;
- mDrawSpotShadow = spotColor.a > 0.f;
-
- // Generate geometries and find number of vertices to generate
- if (mDrawAmbientShadow) {
- mAmbientShadowGeometry = getAmbientShadowGeometry(casterRect, casterCornerRadius, casterZ,
- casterIsTranslucent, ambientColor);
- mAmbientShadowVertexCount = getVertexCountForGeometry(*mAmbientShadowGeometry.get());
- mAmbientShadowIndexCount = getIndexCountForGeometry(*mAmbientShadowGeometry.get());
- } else {
- mAmbientShadowVertexCount = 0;
- mAmbientShadowIndexCount = 0;
- }
-
- if (mDrawSpotShadow) {
- mSpotShadowGeometry =
- getSpotShadowGeometry(casterRect, casterCornerRadius, casterZ, casterIsTranslucent,
- spotColor, lightPosition, lightRadius);
- mSpotShadowVertexCount = getVertexCountForGeometry(*mSpotShadowGeometry.get());
- mSpotShadowIndexCount = getIndexCountForGeometry(*mSpotShadowGeometry.get());
- } else {
- mSpotShadowVertexCount = 0;
- mSpotShadowIndexCount = 0;
- }
-}
-
-size_t GLShadowVertexGenerator::getVertexCount() const {
- return mAmbientShadowVertexCount + mSpotShadowVertexCount;
-}
-
-size_t GLShadowVertexGenerator::getIndexCount() const {
- return mAmbientShadowIndexCount + mSpotShadowIndexCount;
-}
-
-void GLShadowVertexGenerator::fillVertices(Mesh::VertexArray<vec2>& position,
- Mesh::VertexArray<vec4>& color,
- Mesh::VertexArray<vec3>& params) const {
- if (mDrawAmbientShadow) {
- fillVerticesForGeometry(*mAmbientShadowGeometry.get(), mAmbientShadowVertexCount, position,
- color, params);
- }
- if (mDrawSpotShadow) {
- fillVerticesForGeometry(*mSpotShadowGeometry.get(), mSpotShadowVertexCount,
- Mesh::VertexArray<vec2>(position, mAmbientShadowVertexCount),
- Mesh::VertexArray<vec4>(color, mAmbientShadowVertexCount),
- Mesh::VertexArray<vec3>(params, mAmbientShadowVertexCount));
- }
-}
-
-void GLShadowVertexGenerator::fillIndices(uint16_t* indices) const {
- if (mDrawAmbientShadow) {
- fillIndicesForGeometry(*mAmbientShadowGeometry.get(), mAmbientShadowIndexCount,
- 0 /* starting vertex offset */, indices);
- }
- if (mDrawSpotShadow) {
- fillIndicesForGeometry(*mSpotShadowGeometry.get(), mSpotShadowIndexCount,
- mAmbientShadowVertexCount /* starting vertex offset */,
- &(indices[mAmbientShadowIndexCount]));
- }
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLShadowVertexGenerator.h b/libs/renderengine/gl/GLShadowVertexGenerator.h
deleted file mode 100644
index 112f97623b..0000000000
--- a/libs/renderengine/gl/GLShadowVertexGenerator.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2019 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.
- */
-
-#pragma once
-
-#include <math/vec4.h>
-#include <ui/Rect.h>
-
-#include "GLSkiaShadowPort.h"
-
-namespace android {
-namespace renderengine {
-
-class Mesh;
-
-namespace gl {
-
-/**
- * Generates gl attributes required to draw shadow spot and/or ambient shadows.
- *
- * Each shadow can support different colors. This class generates three vertex attributes for
- * each shadow, its position, color and shadow params(offset and distance). These can be sent
- * using a single glDrawElements call.
- */
-class GLShadowVertexGenerator {
-public:
- GLShadowVertexGenerator(const FloatRect& casterRect, float casterCornerRadius, float casterZ,
- bool casterIsTranslucent, const vec4& ambientColor,
- const vec4& spotColor, const vec3& lightPosition, float lightRadius);
- ~GLShadowVertexGenerator() = default;
-
- size_t getVertexCount() const;
- size_t getIndexCount() const;
- void fillVertices(Mesh::VertexArray<vec2>& position, Mesh::VertexArray<vec4>& color,
- Mesh::VertexArray<vec3>& params) const;
- void fillIndices(uint16_t* indices) const;
-
-private:
- bool mDrawAmbientShadow;
- std::unique_ptr<Geometry> mAmbientShadowGeometry;
- int mAmbientShadowVertexCount = 0;
- int mAmbientShadowIndexCount = 0;
-
- bool mDrawSpotShadow;
- std::unique_ptr<Geometry> mSpotShadowGeometry;
- int mSpotShadowVertexCount = 0;
- int mSpotShadowIndexCount = 0;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLSkiaShadowPort.cpp b/libs/renderengine/gl/GLSkiaShadowPort.cpp
deleted file mode 100644
index da8b435854..0000000000
--- a/libs/renderengine/gl/GLSkiaShadowPort.cpp
+++ /dev/null
@@ -1,656 +0,0 @@
-/*
- * Copyright 2019 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.
- */
-
-#include <math/vec4.h>
-
-#include <renderengine/Mesh.h>
-
-#include <ui/Rect.h>
-#include <ui/Transform.h>
-
-#include <utils/Log.h>
-
-#include "GLSkiaShadowPort.h"
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-/**
- * The shadow geometry logic and vertex generation code has been ported from skia shadow
- * fast path OpenGL implementation to draw shadows around rects and rounded rects including
- * circles.
- *
- * path: skia/src/gpu/GrRenderTargetContext.cpp GrRenderTargetContext::drawFastShadow
- *
- * Modifications made:
- * - Switched to using std lib math functions
- * - Fall off function is implemented in vertex shader rather than a shadow texture
- * - Removed transformations applied on the caster rect since the caster will be in local
- * coordinate space and will be transformed by the vertex shader.
- */
-
-static inline float divide_and_pin(float numer, float denom, float min, float max) {
- if (denom == 0.0f) return min;
- return std::clamp(numer / denom, min, max);
-}
-
-static constexpr auto SK_ScalarSqrt2 = 1.41421356f;
-static constexpr auto kAmbientHeightFactor = 1.0f / 128.0f;
-static constexpr auto kAmbientGeomFactor = 64.0f;
-// Assuming that we have a light height of 600 for the spot shadow,
-// the spot values will reach their maximum at a height of approximately 292.3077.
-// We'll round up to 300 to keep it simple.
-static constexpr auto kMaxAmbientRadius = 300 * kAmbientHeightFactor * kAmbientGeomFactor;
-
-inline float AmbientBlurRadius(float height) {
- return std::min(height * kAmbientHeightFactor * kAmbientGeomFactor, kMaxAmbientRadius);
-}
-inline float AmbientRecipAlpha(float height) {
- return 1.0f + std::max(height * kAmbientHeightFactor, 0.0f);
-}
-
-//////////////////////////////////////////////////////////////////////////////
-// Circle Data
-//
-// We have two possible cases for geometry for a circle:
-
-// In the case of a normal fill, we draw geometry for the circle as an octagon.
-static const uint16_t gFillCircleIndices[] = {
- // enter the octagon
- // clang-format off
- 0, 1, 8, 1, 2, 8,
- 2, 3, 8, 3, 4, 8,
- 4, 5, 8, 5, 6, 8,
- 6, 7, 8, 7, 0, 8,
- // clang-format on
-};
-
-// For stroked circles, we use two nested octagons.
-static const uint16_t gStrokeCircleIndices[] = {
- // enter the octagon
- // clang-format off
- 0, 1, 9, 0, 9, 8,
- 1, 2, 10, 1, 10, 9,
- 2, 3, 11, 2, 11, 10,
- 3, 4, 12, 3, 12, 11,
- 4, 5, 13, 4, 13, 12,
- 5, 6, 14, 5, 14, 13,
- 6, 7, 15, 6, 15, 14,
- 7, 0, 8, 7, 8, 15,
- // clang-format on
-};
-
-#define SK_ARRAY_COUNT(a) (sizeof(a) / sizeof((a)[0]))
-static const int kIndicesPerFillCircle = SK_ARRAY_COUNT(gFillCircleIndices);
-static const int kIndicesPerStrokeCircle = SK_ARRAY_COUNT(gStrokeCircleIndices);
-static const int kVertsPerStrokeCircle = 16;
-static const int kVertsPerFillCircle = 9;
-
-static int circle_type_to_vert_count(bool stroked) {
- return stroked ? kVertsPerStrokeCircle : kVertsPerFillCircle;
-}
-
-static int circle_type_to_index_count(bool stroked) {
- return stroked ? kIndicesPerStrokeCircle : kIndicesPerFillCircle;
-}
-
-static const uint16_t* circle_type_to_indices(bool stroked) {
- return stroked ? gStrokeCircleIndices : gFillCircleIndices;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// RoundRect Data
-//
-// The geometry for a shadow roundrect is similar to a 9-patch:
-// ____________
-// |_|________|_|
-// | | | |
-// | | | |
-// | | | |
-// |_|________|_|
-// |_|________|_|
-//
-// However, each corner is rendered as a fan rather than a simple quad, as below. (The diagram
-// shows the upper part of the upper left corner. The bottom triangle would similarly be split
-// into two triangles.)
-// ________
-// |\ \ |
-// | \ \ |
-// | \\ |
-// | \|
-// --------
-//
-// The center of the fan handles the curve of the corner. For roundrects where the stroke width
-// is greater than the corner radius, the outer triangles blend from the curve to the straight
-// sides. Otherwise these triangles will be degenerate.
-//
-// In the case where the stroke width is greater than the corner radius and the
-// blur radius (overstroke), we add additional geometry to mark out the rectangle in the center.
-// This rectangle extends the coverage values of the center edges of the 9-patch.
-// ____________
-// |_|________|_|
-// | |\ ____ /| |
-// | | | | | |
-// | | |____| | |
-// |_|/______\|_|
-// |_|________|_|
-//
-// For filled rrects we reuse the stroke geometry but add an additional quad to the center.
-
-static const uint16_t gRRectIndices[] = {
- // clang-format off
- // overstroke quads
- // we place this at the beginning so that we can skip these indices when rendering as filled
- 0, 6, 25, 0, 25, 24,
- 6, 18, 27, 6, 27, 25,
- 18, 12, 26, 18, 26, 27,
- 12, 0, 24, 12, 24, 26,
-
- // corners
- 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5,
- 6, 11, 10, 6, 10, 9, 6, 9, 8, 6, 8, 7,
- 12, 17, 16, 12, 16, 15, 12, 15, 14, 12, 14, 13,
- 18, 19, 20, 18, 20, 21, 18, 21, 22, 18, 22, 23,
-
- // edges
- 0, 5, 11, 0, 11, 6,
- 6, 7, 19, 6, 19, 18,
- 18, 23, 17, 18, 17, 12,
- 12, 13, 1, 12, 1, 0,
-
- // fill quad
- // we place this at the end so that we can skip these indices when rendering as stroked
- 0, 6, 18, 0, 18, 12,
- // clang-format on
-};
-
-// overstroke count
-static const int kIndicesPerOverstrokeRRect = SK_ARRAY_COUNT(gRRectIndices) - 6;
-// simple stroke count skips overstroke indices
-static const int kIndicesPerStrokeRRect = kIndicesPerOverstrokeRRect - 6 * 4;
-// fill count adds final quad to stroke count
-static const int kIndicesPerFillRRect = kIndicesPerStrokeRRect + 6;
-static const int kVertsPerStrokeRRect = 24;
-static const int kVertsPerOverstrokeRRect = 28;
-static const int kVertsPerFillRRect = 24;
-
-static int rrect_type_to_vert_count(RRectType type) {
- switch (type) {
- case kFill_RRectType:
- return kVertsPerFillRRect;
- case kStroke_RRectType:
- return kVertsPerStrokeRRect;
- case kOverstroke_RRectType:
- return kVertsPerOverstrokeRRect;
- }
- ALOGE("Invalid rect type: %d", type);
- return -1;
-}
-
-static int rrect_type_to_index_count(RRectType type) {
- switch (type) {
- case kFill_RRectType:
- return kIndicesPerFillRRect;
- case kStroke_RRectType:
- return kIndicesPerStrokeRRect;
- case kOverstroke_RRectType:
- return kIndicesPerOverstrokeRRect;
- }
- ALOGE("Invalid rect type: %d", type);
- return -1;
-}
-
-static const uint16_t* rrect_type_to_indices(RRectType type) {
- switch (type) {
- case kFill_RRectType:
- case kStroke_RRectType:
- return gRRectIndices + 6 * 4;
- case kOverstroke_RRectType:
- return gRRectIndices;
- }
- ALOGE("Invalid rect type: %d", type);
- return nullptr;
-}
-
-static void fillInCircleVerts(const Geometry& args, bool isStroked,
- Mesh::VertexArray<vec2>& position,
- Mesh::VertexArray<vec4>& shadowColor,
- Mesh::VertexArray<vec3>& shadowParams) {
- vec4 color = args.fColor;
- float outerRadius = args.fOuterRadius;
- float innerRadius = args.fInnerRadius;
- float blurRadius = args.fBlurRadius;
- float distanceCorrection = outerRadius / blurRadius;
-
- const FloatRect& bounds = args.fDevBounds;
-
- // The inner radius in the vertex data must be specified in normalized space.
- innerRadius = innerRadius / outerRadius;
-
- vec2 center = vec2(bounds.getWidth() / 2.0f, bounds.getHeight() / 2.0f);
- float halfWidth = 0.5f * bounds.getWidth();
- float octOffset = 0.41421356237f; // sqrt(2) - 1
- int vertexCount = 0;
-
- position[vertexCount] = center + vec2(-octOffset * halfWidth, -halfWidth);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(-octOffset, -1, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(octOffset * halfWidth, -halfWidth);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(octOffset, -1, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(halfWidth, -octOffset * halfWidth);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(1, -octOffset, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(halfWidth, octOffset * halfWidth);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(1, octOffset, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(octOffset * halfWidth, halfWidth);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(octOffset, 1, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(-octOffset * halfWidth, halfWidth);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(-octOffset, 1, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(-halfWidth, octOffset * halfWidth);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(-1, octOffset, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(-halfWidth, -octOffset * halfWidth);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(-1, -octOffset, distanceCorrection);
- vertexCount++;
-
- if (isStroked) {
- // compute the inner ring
-
- // cosine and sine of pi/8
- float c = 0.923579533f;
- float s = 0.382683432f;
- float r = args.fInnerRadius;
-
- position[vertexCount] = center + vec2(-s * r, -c * r);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(-s * innerRadius, -c * innerRadius, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(s * r, -c * r);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(s * innerRadius, -c * innerRadius, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(c * r, -s * r);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(c * innerRadius, -s * innerRadius, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(c * r, s * r);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(c * innerRadius, s * innerRadius, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(s * r, c * r);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(s * innerRadius, c * innerRadius, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(-s * r, c * r);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(-s * innerRadius, c * innerRadius, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(-c * r, s * r);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(-c * innerRadius, s * innerRadius, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(-c * r, -s * r);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(-c * innerRadius, -s * innerRadius, distanceCorrection);
- vertexCount++;
- } else {
- // filled
- position[vertexCount] = center;
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
- vertexCount++;
- }
-}
-
-static void fillInRRectVerts(const Geometry& args, Mesh::VertexArray<vec2>& position,
- Mesh::VertexArray<vec4>& shadowColor,
- Mesh::VertexArray<vec3>& shadowParams) {
- vec4 color = args.fColor;
- float outerRadius = args.fOuterRadius;
-
- const FloatRect& bounds = args.fDevBounds;
-
- float umbraInset = args.fUmbraInset;
- float minDim = 0.5f * std::min(bounds.getWidth(), bounds.getHeight());
- if (umbraInset > minDim) {
- umbraInset = minDim;
- }
-
- float xInner[4] = {bounds.left + umbraInset, bounds.right - umbraInset,
- bounds.left + umbraInset, bounds.right - umbraInset};
- float xMid[4] = {bounds.left + outerRadius, bounds.right - outerRadius,
- bounds.left + outerRadius, bounds.right - outerRadius};
- float xOuter[4] = {bounds.left, bounds.right, bounds.left, bounds.right};
- float yInner[4] = {bounds.top + umbraInset, bounds.top + umbraInset, bounds.bottom - umbraInset,
- bounds.bottom - umbraInset};
- float yMid[4] = {bounds.top + outerRadius, bounds.top + outerRadius,
- bounds.bottom - outerRadius, bounds.bottom - outerRadius};
- float yOuter[4] = {bounds.top, bounds.top, bounds.bottom, bounds.bottom};
-
- float blurRadius = args.fBlurRadius;
-
- // In the case where we have to inset more for the umbra, our two triangles in the
- // corner get skewed to a diamond rather than a square. To correct for that,
- // we also skew the vectors we send to the shader that help define the circle.
- // By doing so, we end up with a quarter circle in the corner rather than the
- // elliptical curve.
-
- // This is a bit magical, but it gives us the correct results at extrema:
- // a) umbraInset == outerRadius produces an orthogonal vector
- // b) outerRadius == 0 produces a diagonal vector
- // And visually the corner looks correct.
- vec2 outerVec = vec2(outerRadius - umbraInset, -outerRadius - umbraInset);
- outerVec = normalize(outerVec);
- // We want the circle edge to fall fractionally along the diagonal at
- // (sqrt(2)*(umbraInset - outerRadius) + outerRadius)/sqrt(2)*umbraInset
- //
- // Setting the components of the diagonal offset to the following value will give us that.
- float diagVal = umbraInset / (SK_ScalarSqrt2 * (outerRadius - umbraInset) - outerRadius);
- vec2 diagVec = vec2(diagVal, diagVal);
- float distanceCorrection = umbraInset / blurRadius;
-
- int vertexCount = 0;
- // build corner by corner
- for (int i = 0; i < 4; ++i) {
- // inner point
- position[vertexCount] = vec2(xInner[i], yInner[i]);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
- vertexCount++;
-
- // outer points
- position[vertexCount] = vec2(xOuter[i], yInner[i]);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(0, -1, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = vec2(xOuter[i], yMid[i]);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(outerVec.x, outerVec.y, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = vec2(xOuter[i], yOuter[i]);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(diagVec.x, diagVec.y, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = vec2(xMid[i], yOuter[i]);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(outerVec.x, outerVec.y, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = vec2(xInner[i], yOuter[i]);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(0, -1, distanceCorrection);
- vertexCount++;
- }
-
- // Add the additional vertices for overstroked rrects.
- // Effectively this is an additional stroked rrect, with its
- // parameters equal to those in the center of the 9-patch. This will
- // give constant values across this inner ring.
- if (kOverstroke_RRectType == args.fType) {
- float inset = umbraInset + args.fInnerRadius;
-
- // TL
- position[vertexCount] = vec2(bounds.left + inset, bounds.top + inset);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
- vertexCount++;
-
- // TR
- position[vertexCount] = vec2(bounds.right - inset, bounds.top + inset);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
- vertexCount++;
-
- // BL
- position[vertexCount] = vec2(bounds.left + inset, bounds.bottom - inset);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
- vertexCount++;
-
- // BR
- position[vertexCount] = vec2(bounds.right - inset, bounds.bottom - inset);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
- vertexCount++;
- }
-}
-
-int getVertexCountForGeometry(const Geometry& shadowGeometry) {
- if (shadowGeometry.fIsCircle) {
- return circle_type_to_vert_count(shadowGeometry.fType);
- }
-
- return rrect_type_to_vert_count(shadowGeometry.fType);
-}
-
-int getIndexCountForGeometry(const Geometry& shadowGeometry) {
- if (shadowGeometry.fIsCircle) {
- return circle_type_to_index_count(kStroke_RRectType == shadowGeometry.fType);
- }
-
- return rrect_type_to_index_count(shadowGeometry.fType);
-}
-
-void fillVerticesForGeometry(const Geometry& shadowGeometry, int /* vertexCount */,
- Mesh::VertexArray<vec2> position, Mesh::VertexArray<vec4> shadowColor,
- Mesh::VertexArray<vec3> shadowParams) {
- if (shadowGeometry.fIsCircle) {
- fillInCircleVerts(shadowGeometry, shadowGeometry.fIsStroked, position, shadowColor,
- shadowParams);
- } else {
- fillInRRectVerts(shadowGeometry, position, shadowColor, shadowParams);
- }
-}
-
-void fillIndicesForGeometry(const Geometry& shadowGeometry, int indexCount,
- int startingVertexOffset, uint16_t* indices) {
- if (shadowGeometry.fIsCircle) {
- const uint16_t* primIndices = circle_type_to_indices(shadowGeometry.fIsStroked);
- for (int i = 0; i < indexCount; ++i) {
- indices[i] = primIndices[i] + startingVertexOffset;
- }
- } else {
- const uint16_t* primIndices = rrect_type_to_indices(shadowGeometry.fType);
- for (int i = 0; i < indexCount; ++i) {
- indices[i] = primIndices[i] + startingVertexOffset;
- }
- }
-}
-
-inline void GetSpotParams(float occluderZ, float lightX, float lightY, float lightZ,
- float lightRadius, float& blurRadius, float& scale, vec2& translate) {
- float zRatio = divide_and_pin(occluderZ, lightZ - occluderZ, 0.0f, 0.95f);
- blurRadius = lightRadius * zRatio;
- scale = divide_and_pin(lightZ, lightZ - occluderZ, 1.0f, 1.95f);
- translate.x = -zRatio * lightX;
- translate.y = -zRatio * lightY;
-}
-
-static std::unique_ptr<Geometry> getShadowGeometry(const vec4& color, const FloatRect& devRect,
- float devRadius, float blurRadius,
- float insetWidth) {
- // An insetWidth > 1/2 rect width or height indicates a simple fill.
- const bool isCircle = ((devRadius >= devRect.getWidth()) && (devRadius >= devRect.getHeight()));
-
- FloatRect bounds = devRect;
- float innerRadius = 0.0f;
- float outerRadius = devRadius;
- float umbraInset;
-
- RRectType type = kFill_RRectType;
- if (isCircle) {
- umbraInset = 0;
- } else {
- umbraInset = std::max(outerRadius, blurRadius);
- }
-
- // If stroke is greater than width or height, this is still a fill,
- // otherwise we compute stroke params.
- if (isCircle) {
- innerRadius = devRadius - insetWidth;
- type = innerRadius > 0 ? kStroke_RRectType : kFill_RRectType;
- } else {
- if (insetWidth <= 0.5f * std::min(devRect.getWidth(), devRect.getHeight())) {
- // We don't worry about a real inner radius, we just need to know if we
- // need to create overstroke vertices.
- innerRadius = std::max(insetWidth - umbraInset, 0.0f);
- type = innerRadius > 0 ? kOverstroke_RRectType : kStroke_RRectType;
- }
- }
- const bool isStroked = (kStroke_RRectType == type);
- return std::make_unique<Geometry>(Geometry{color, outerRadius, umbraInset, innerRadius,
- blurRadius, bounds, type, isCircle, isStroked});
-}
-
-std::unique_ptr<Geometry> getAmbientShadowGeometry(const FloatRect& casterRect,
- float casterCornerRadius, float casterZ,
- bool casterIsTranslucent,
- const vec4& ambientColor) {
- float devSpaceInsetWidth = AmbientBlurRadius(casterZ);
- const float umbraRecipAlpha = AmbientRecipAlpha(casterZ);
- const float devSpaceAmbientBlur = devSpaceInsetWidth * umbraRecipAlpha;
-
- // Outset the shadow rrect to the border of the penumbra
- float ambientPathOutset = devSpaceInsetWidth;
- FloatRect outsetRect(casterRect);
- outsetRect.left -= ambientPathOutset;
- outsetRect.top -= ambientPathOutset;
- outsetRect.right += ambientPathOutset;
- outsetRect.bottom += ambientPathOutset;
-
- float outsetRad = casterCornerRadius + ambientPathOutset;
- if (casterIsTranslucent) {
- // set a large inset to force a fill
- devSpaceInsetWidth = outsetRect.getWidth();
- }
-
- return getShadowGeometry(ambientColor, outsetRect, std::abs(outsetRad), devSpaceAmbientBlur,
- std::abs(devSpaceInsetWidth));
-}
-
-std::unique_ptr<Geometry> getSpotShadowGeometry(const FloatRect& casterRect,
- float casterCornerRadius, float casterZ,
- bool casterIsTranslucent, const vec4& spotColor,
- const vec3& lightPosition, float lightRadius) {
- float devSpaceSpotBlur;
- float spotScale;
- vec2 spotOffset;
- GetSpotParams(casterZ, lightPosition.x, lightPosition.y, lightPosition.z, lightRadius,
- devSpaceSpotBlur, spotScale, spotOffset);
- // handle scale of radius due to CTM
- const float srcSpaceSpotBlur = devSpaceSpotBlur;
-
- // Adjust translate for the effect of the scale.
- spotOffset.x += spotScale;
- spotOffset.y += spotScale;
-
- // Compute the transformed shadow rect
- ui::Transform shadowTransform;
- shadowTransform.set(spotOffset.x, spotOffset.y);
- shadowTransform.set(spotScale, 0, 0, spotScale);
- FloatRect spotShadowRect = shadowTransform.transform(casterRect);
- float spotShadowRadius = casterCornerRadius * spotScale;
-
- // Compute the insetWidth
- float blurOutset = srcSpaceSpotBlur;
- float insetWidth = blurOutset;
- if (casterIsTranslucent) {
- // If transparent, just do a fill
- insetWidth += spotShadowRect.getWidth();
- } else {
- // For shadows, instead of using a stroke we specify an inset from the penumbra
- // border. We want to extend this inset area so that it meets up with the caster
- // geometry. The inset geometry will by default already be inset by the blur width.
- //
- // We compare the min and max corners inset by the radius between the original
- // rrect and the shadow rrect. The distance between the two plus the difference
- // between the scaled radius and the original radius gives the distance from the
- // transformed shadow shape to the original shape in that corner. The max
- // of these gives the maximum distance we need to cover.
- //
- // Since we are outsetting by 1/2 the blur distance, we just add the maxOffset to
- // that to get the full insetWidth.
- float maxOffset;
- if (casterCornerRadius <= 0.f) {
- // Manhattan distance works better for rects
- maxOffset = std::max(std::max(std::abs(spotShadowRect.left - casterRect.left),
- std::abs(spotShadowRect.top - casterRect.top)),
- std::max(std::abs(spotShadowRect.right - casterRect.right),
- std::abs(spotShadowRect.bottom - casterRect.bottom)));
- } else {
- float dr = spotShadowRadius - casterCornerRadius;
- vec2 upperLeftOffset = vec2(spotShadowRect.left - casterRect.left + dr,
- spotShadowRect.top - casterRect.top + dr);
- vec2 lowerRightOffset = vec2(spotShadowRect.right - casterRect.right - dr,
- spotShadowRect.bottom - casterRect.bottom - dr);
- maxOffset = sqrt(std::max(dot(upperLeftOffset, lowerRightOffset),
- dot(lowerRightOffset, lowerRightOffset))) +
- dr;
- }
- insetWidth += std::max(blurOutset, maxOffset);
- }
-
- // Outset the shadow rrect to the border of the penumbra
- spotShadowRadius += blurOutset;
- spotShadowRect.left -= blurOutset;
- spotShadowRect.top -= blurOutset;
- spotShadowRect.right += blurOutset;
- spotShadowRect.bottom += blurOutset;
-
- return getShadowGeometry(spotColor, spotShadowRect, std::abs(spotShadowRadius),
- 2.0f * devSpaceSpotBlur, std::abs(insetWidth));
-}
-
-void fillShadowTextureData(uint8_t* data, size_t shadowTextureWidth) {
- for (int i = 0; i < shadowTextureWidth; i++) {
- const float d = 1 - i / ((shadowTextureWidth * 1.0f) - 1.0f);
- data[i] = static_cast<uint8_t>((exp(-4.0f * d * d) - 0.018f) * 255);
- }
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLSkiaShadowPort.h b/libs/renderengine/gl/GLSkiaShadowPort.h
deleted file mode 100644
index 912c8bb7b3..0000000000
--- a/libs/renderengine/gl/GLSkiaShadowPort.h
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright 2019 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.
- */
-
-#pragma once
-
-#include <math/vec4.h>
-#include <renderengine/Mesh.h>
-#include <ui/Rect.h>
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-/**
- * The shadow geometry logic and vertex generation code has been ported from skia shadow
- * fast path OpenGL implementation to draw shadows around rects and rounded rects including
- * circles.
- *
- * path: skia/src/gpu/GrRenderTargetContext.cpp GrRenderTargetContext::drawFastShadow
- *
- * Modifications made:
- * - Switched to using std lib math functions
- * - Fall off function is implemented in vertex shader rather than a shadow texture
- * - Removed transformations applied on the caster rect since the caster will be in local
- * coordinate space and will be transformed by the vertex shader.
- */
-
-enum RRectType {
- kFill_RRectType,
- kStroke_RRectType,
- kOverstroke_RRectType,
-};
-
-struct Geometry {
- vec4 fColor;
- float fOuterRadius;
- float fUmbraInset;
- float fInnerRadius;
- float fBlurRadius;
- FloatRect fDevBounds;
- RRectType fType;
- bool fIsCircle;
- bool fIsStroked;
-};
-
-std::unique_ptr<Geometry> getSpotShadowGeometry(const FloatRect& casterRect,
- float casterCornerRadius, float casterZ,
- bool casterIsTranslucent, const vec4& spotColor,
- const vec3& lightPosition, float lightRadius);
-
-std::unique_ptr<Geometry> getAmbientShadowGeometry(const FloatRect& casterRect,
- float casterCornerRadius, float casterZ,
- bool casterIsTranslucent,
- const vec4& ambientColor);
-
-int getVertexCountForGeometry(const Geometry& shadowGeometry);
-
-int getIndexCountForGeometry(const Geometry& shadowGeometry);
-
-void fillVerticesForGeometry(const Geometry& shadowGeometry, int vertexCount,
- Mesh::VertexArray<vec2> position, Mesh::VertexArray<vec4> shadowColor,
- Mesh::VertexArray<vec3> shadowParams);
-
-void fillIndicesForGeometry(const Geometry& shadowGeometry, int indexCount,
- int startingVertexOffset, uint16_t* indices);
-
-/**
- * Maps shadow geometry 'alpha' varying (1 for darkest, 0 for transparent) to
- * darkness at that spot. Values are determined by an exponential falloff
- * function provided by UX.
- *
- * The texture is used for quick lookup in theshadow shader.
- *
- * textureData - filled with shadow texture data that needs to be at least of
- * size textureWidth
- *
- * textureWidth - width of the texture, height is always 1
- */
-void fillShadowTextureData(uint8_t* textureData, size_t textureWidth);
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLVertexBuffer.cpp b/libs/renderengine/gl/GLVertexBuffer.cpp
deleted file mode 100644
index e50c471b6d..0000000000
--- a/libs/renderengine/gl/GLVertexBuffer.cpp
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2020 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 ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "GLVertexBuffer.h"
-
-#include <GLES/gl.h>
-#include <GLES2/gl2.h>
-#include <nativebase/nativebase.h>
-#include <utils/Trace.h>
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-GLVertexBuffer::GLVertexBuffer() {
- glGenBuffers(1, &mBufferName);
-}
-
-GLVertexBuffer::~GLVertexBuffer() {
- glDeleteBuffers(1, &mBufferName);
-}
-
-void GLVertexBuffer::allocateBuffers(const GLfloat data[], const GLuint size) {
- ATRACE_CALL();
- bind();
- glBufferData(GL_ARRAY_BUFFER, size * sizeof(GLfloat), data, GL_STATIC_DRAW);
- unbind();
-}
-
-void GLVertexBuffer::bind() const {
- glBindBuffer(GL_ARRAY_BUFFER, mBufferName);
-}
-
-void GLVertexBuffer::unbind() const {
- glBindBuffer(GL_ARRAY_BUFFER, 0);
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLVertexBuffer.h b/libs/renderengine/gl/GLVertexBuffer.h
deleted file mode 100644
index c0fd0c1b04..0000000000
--- a/libs/renderengine/gl/GLVertexBuffer.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2020 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.
- */
-
-#pragma once
-
-#include <cstdint>
-
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <GLES2/gl2.h>
-
-struct ANativeWindowBuffer;
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-class GLESRenderEngine;
-
-class GLVertexBuffer {
-public:
- explicit GLVertexBuffer();
- ~GLVertexBuffer();
-
- void allocateBuffers(const GLfloat data[], const GLuint size);
- uint32_t getBufferName() const { return mBufferName; }
- void bind() const;
- void unbind() const;
-
-private:
- uint32_t mBufferName;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/ImageManager.cpp b/libs/renderengine/gl/ImageManager.cpp
deleted file mode 100644
index 62566494f0..0000000000
--- a/libs/renderengine/gl/ImageManager.cpp
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright 2019 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_NDEBUG 0
-#undef LOG_TAG
-#define LOG_TAG "RenderEngine"
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include <pthread.h>
-
-#include <processgroup/sched_policy.h>
-#include <utils/Trace.h>
-#include "GLESRenderEngine.h"
-#include "ImageManager.h"
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-ImageManager::ImageManager(GLESRenderEngine* engine) : mEngine(engine) {}
-
-void ImageManager::initThread() {
- mThread = std::thread([this]() { threadMain(); });
- pthread_setname_np(mThread.native_handle(), "ImageManager");
- // Use SCHED_FIFO to minimize jitter
- struct sched_param param = {0};
- param.sched_priority = 2;
- if (pthread_setschedparam(mThread.native_handle(), SCHED_FIFO, &param) != 0) {
- ALOGE("Couldn't set SCHED_FIFO for ImageManager");
- }
-}
-
-ImageManager::~ImageManager() {
- {
- std::lock_guard<std::mutex> lock(mMutex);
- mRunning = false;
- }
- mCondition.notify_all();
- if (mThread.joinable()) {
- mThread.join();
- }
-}
-
-void ImageManager::cacheAsync(const sp<GraphicBuffer>& buffer,
- const std::shared_ptr<Barrier>& barrier) {
- if (buffer == nullptr) {
- {
- std::lock_guard<std::mutex> lock(barrier->mutex);
- barrier->isOpen = true;
- barrier->result = BAD_VALUE;
- }
- barrier->condition.notify_one();
- return;
- }
- ATRACE_CALL();
- QueueEntry entry = {QueueEntry::Operation::Insert, buffer, buffer->getId(), barrier};
- queueOperation(std::move(entry));
-}
-
-status_t ImageManager::cache(const sp<GraphicBuffer>& buffer) {
- ATRACE_CALL();
- auto barrier = std::make_shared<Barrier>();
- cacheAsync(buffer, barrier);
- std::lock_guard<std::mutex> lock(barrier->mutex);
- barrier->condition.wait(barrier->mutex,
- [&]() REQUIRES(barrier->mutex) { return barrier->isOpen; });
- return barrier->result;
-}
-
-void ImageManager::releaseAsync(uint64_t bufferId, const std::shared_ptr<Barrier>& barrier) {
- ATRACE_CALL();
- QueueEntry entry = {QueueEntry::Operation::Delete, nullptr, bufferId, barrier};
- queueOperation(std::move(entry));
-}
-
-void ImageManager::queueOperation(const QueueEntry&& entry) {
- {
- std::lock_guard<std::mutex> lock(mMutex);
- mQueue.emplace(entry);
- ATRACE_INT("ImageManagerQueueDepth", mQueue.size());
- }
- mCondition.notify_one();
-}
-
-void ImageManager::threadMain() {
- set_sched_policy(0, SP_FOREGROUND);
- bool run;
- {
- std::lock_guard<std::mutex> lock(mMutex);
- run = mRunning;
- }
- while (run) {
- QueueEntry entry;
- {
- std::lock_guard<std::mutex> lock(mMutex);
- mCondition.wait(mMutex,
- [&]() REQUIRES(mMutex) { return !mQueue.empty() || !mRunning; });
- run = mRunning;
-
- if (!mRunning) {
- // if mRunning is false, then ImageManager is being destroyed, so
- // bail out now.
- break;
- }
-
- entry = mQueue.front();
- mQueue.pop();
- ATRACE_INT("ImageManagerQueueDepth", mQueue.size());
- }
-
- status_t result = NO_ERROR;
- switch (entry.op) {
- case QueueEntry::Operation::Delete:
- mEngine->unbindExternalTextureBufferInternal(entry.bufferId);
- break;
- case QueueEntry::Operation::Insert:
- result = mEngine->cacheExternalTextureBufferInternal(entry.buffer);
- break;
- }
- if (entry.barrier != nullptr) {
- {
- std::lock_guard<std::mutex> entryLock(entry.barrier->mutex);
- entry.barrier->result = result;
- entry.barrier->isOpen = true;
- }
- entry.barrier->condition.notify_one();
- }
- }
-
- ALOGD("Reached end of threadMain, terminating ImageManager thread!");
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/ImageManager.h b/libs/renderengine/gl/ImageManager.h
deleted file mode 100644
index be67de8367..0000000000
--- a/libs/renderengine/gl/ImageManager.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2019 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.
- */
-
-#pragma once
-
-#include <condition_variable>
-#include <mutex>
-#include <queue>
-#include <thread>
-
-#include <ui/GraphicBuffer.h>
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-class GLESRenderEngine;
-
-class ImageManager {
-public:
- struct Barrier {
- std::mutex mutex;
- std::condition_variable_any condition;
- bool isOpen GUARDED_BY(mutex) = false;
- status_t result GUARDED_BY(mutex) = NO_ERROR;
- };
- ImageManager(GLESRenderEngine* engine);
- ~ImageManager();
- // Starts the background thread for the ImageManager
- // We need this to guarantee that the class is fully-constructed before the
- // thread begins running.
- void initThread();
- void cacheAsync(const sp<GraphicBuffer>& buffer, const std::shared_ptr<Barrier>& barrier)
- EXCLUDES(mMutex);
- status_t cache(const sp<GraphicBuffer>& buffer);
- void releaseAsync(uint64_t bufferId, const std::shared_ptr<Barrier>& barrier) EXCLUDES(mMutex);
-
-private:
- struct QueueEntry {
- enum class Operation { Delete, Insert };
-
- Operation op = Operation::Delete;
- sp<GraphicBuffer> buffer = nullptr;
- uint64_t bufferId = 0;
- std::shared_ptr<Barrier> barrier = nullptr;
- };
-
- void queueOperation(const QueueEntry&& entry);
- void threadMain();
- GLESRenderEngine* const mEngine;
- std::thread mThread;
- std::condition_variable_any mCondition;
- std::mutex mMutex;
- std::queue<QueueEntry> mQueue GUARDED_BY(mMutex);
-
- bool mRunning GUARDED_BY(mMutex) = true;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/Program.cpp b/libs/renderengine/gl/Program.cpp
deleted file mode 100644
index 26f6166761..0000000000
--- a/libs/renderengine/gl/Program.cpp
+++ /dev/null
@@ -1,175 +0,0 @@
-/*Gluint
- * Copyright 2013 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.
- */
-
-#include "Program.h"
-
-#include <stdint.h>
-
-#include <log/log.h>
-#include <math/mat4.h>
-#include <utils/String8.h>
-#include "ProgramCache.h"
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-Program::Program(const ProgramCache::Key& /*needs*/, const char* vertex, const char* fragment)
- : mInitialized(false) {
- GLuint vertexId = buildShader(vertex, GL_VERTEX_SHADER);
- GLuint fragmentId = buildShader(fragment, GL_FRAGMENT_SHADER);
- GLuint programId = glCreateProgram();
- glAttachShader(programId, vertexId);
- glAttachShader(programId, fragmentId);
- glBindAttribLocation(programId, position, "position");
- glBindAttribLocation(programId, texCoords, "texCoords");
- glBindAttribLocation(programId, cropCoords, "cropCoords");
- glBindAttribLocation(programId, shadowColor, "shadowColor");
- glBindAttribLocation(programId, shadowParams, "shadowParams");
- glLinkProgram(programId);
-
- GLint status;
- glGetProgramiv(programId, GL_LINK_STATUS, &status);
- if (status != GL_TRUE) {
- ALOGE("Error while linking shaders:");
- GLint infoLen = 0;
- glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &infoLen);
- if (infoLen > 1) {
- GLchar log[infoLen];
- glGetProgramInfoLog(programId, infoLen, 0, &log[0]);
- ALOGE("%s", log);
- }
- glDetachShader(programId, vertexId);
- glDetachShader(programId, fragmentId);
- glDeleteShader(vertexId);
- glDeleteShader(fragmentId);
- glDeleteProgram(programId);
- } else {
- mProgram = programId;
- mVertexShader = vertexId;
- mFragmentShader = fragmentId;
- mInitialized = true;
- mProjectionMatrixLoc = glGetUniformLocation(programId, "projection");
- mTextureMatrixLoc = glGetUniformLocation(programId, "texture");
- mSamplerLoc = glGetUniformLocation(programId, "sampler");
- mColorLoc = glGetUniformLocation(programId, "color");
- mDisplayColorMatrixLoc = glGetUniformLocation(programId, "displayColorMatrix");
- mDisplayMaxLuminanceLoc = glGetUniformLocation(programId, "displayMaxLuminance");
- mMaxMasteringLuminanceLoc = glGetUniformLocation(programId, "maxMasteringLuminance");
- mMaxContentLuminanceLoc = glGetUniformLocation(programId, "maxContentLuminance");
- mInputTransformMatrixLoc = glGetUniformLocation(programId, "inputTransformMatrix");
- mOutputTransformMatrixLoc = glGetUniformLocation(programId, "outputTransformMatrix");
- mCornerRadiusLoc = glGetUniformLocation(programId, "cornerRadius");
- mCropCenterLoc = glGetUniformLocation(programId, "cropCenter");
-
- // set-up the default values for our uniforms
- glUseProgram(programId);
- glUniformMatrix4fv(mProjectionMatrixLoc, 1, GL_FALSE, mat4().asArray());
- glEnableVertexAttribArray(0);
- }
-}
-
-Program::~Program() {
- glDetachShader(mProgram, mVertexShader);
- glDetachShader(mProgram, mFragmentShader);
- glDeleteShader(mVertexShader);
- glDeleteShader(mFragmentShader);
- glDeleteProgram(mProgram);
-}
-
-bool Program::isValid() const {
- return mInitialized;
-}
-
-void Program::use() {
- glUseProgram(mProgram);
-}
-
-GLuint Program::getAttrib(const char* name) const {
- // TODO: maybe use a local cache
- return glGetAttribLocation(mProgram, name);
-}
-
-GLint Program::getUniform(const char* name) const {
- // TODO: maybe use a local cache
- return glGetUniformLocation(mProgram, name);
-}
-
-GLuint Program::buildShader(const char* source, GLenum type) {
- GLuint shader = glCreateShader(type);
- glShaderSource(shader, 1, &source, 0);
- glCompileShader(shader);
- GLint status;
- glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
- if (status != GL_TRUE) {
- // Some drivers return wrong values for GL_INFO_LOG_LENGTH
- // use a fixed size instead
- GLchar log[512];
- glGetShaderInfoLog(shader, sizeof(log), 0, log);
- ALOGE("Error while compiling shader: \n%s\n%s", source, log);
- glDeleteShader(shader);
- return 0;
- }
- return shader;
-}
-
-void Program::setUniforms(const Description& desc) {
- // TODO: we should have a mechanism here to not always reset uniforms that
- // didn't change for this program.
-
- if (mSamplerLoc >= 0) {
- glUniform1i(mSamplerLoc, 0);
- glUniformMatrix4fv(mTextureMatrixLoc, 1, GL_FALSE, desc.texture.getMatrix().asArray());
- }
- if (mColorLoc >= 0) {
- const float color[4] = {desc.color.r, desc.color.g, desc.color.b, desc.color.a};
- glUniform4fv(mColorLoc, 1, color);
- }
- if (mDisplayColorMatrixLoc >= 0) {
- glUniformMatrix4fv(mDisplayColorMatrixLoc, 1, GL_FALSE, desc.displayColorMatrix.asArray());
- }
- if (mInputTransformMatrixLoc >= 0) {
- mat4 inputTransformMatrix = desc.inputTransformMatrix;
- glUniformMatrix4fv(mInputTransformMatrixLoc, 1, GL_FALSE, inputTransformMatrix.asArray());
- }
- if (mOutputTransformMatrixLoc >= 0) {
- // The output transform matrix and color matrix can be combined as one matrix
- // that is applied right before applying OETF.
- mat4 outputTransformMatrix = desc.colorMatrix * desc.outputTransformMatrix;
- glUniformMatrix4fv(mOutputTransformMatrixLoc, 1, GL_FALSE, outputTransformMatrix.asArray());
- }
- if (mDisplayMaxLuminanceLoc >= 0) {
- glUniform1f(mDisplayMaxLuminanceLoc, desc.displayMaxLuminance);
- }
- if (mMaxMasteringLuminanceLoc >= 0) {
- glUniform1f(mMaxMasteringLuminanceLoc, desc.maxMasteringLuminance);
- }
- if (mMaxContentLuminanceLoc >= 0) {
- glUniform1f(mMaxContentLuminanceLoc, desc.maxContentLuminance);
- }
- if (mCornerRadiusLoc >= 0) {
- glUniform1f(mCornerRadiusLoc, desc.cornerRadius);
- }
- if (mCropCenterLoc >= 0) {
- glUniform2f(mCropCenterLoc, desc.cropSize.x / 2.0f, desc.cropSize.y / 2.0f);
- }
- // these uniforms are always present
- glUniformMatrix4fv(mProjectionMatrixLoc, 1, GL_FALSE, desc.projectionMatrix.asArray());
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/Program.h b/libs/renderengine/gl/Program.h
deleted file mode 100644
index 41f1bf865e..0000000000
--- a/libs/renderengine/gl/Program.h
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2013 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.
- */
-
-#ifndef SF_RENDER_ENGINE_PROGRAM_H
-#define SF_RENDER_ENGINE_PROGRAM_H
-
-#include <stdint.h>
-
-#include <GLES2/gl2.h>
-#include <renderengine/private/Description.h>
-#include "ProgramCache.h"
-
-namespace android {
-
-class String8;
-
-namespace renderengine {
-namespace gl {
-
-/*
- * Abstracts a GLSL program comprising a vertex and fragment shader
- */
-class Program {
-public:
- // known locations for position and texture coordinates
- enum {
- /* position of each vertex for vertex shader */
- position = 0,
-
- /* UV coordinates for texture mapping */
- texCoords = 1,
-
- /* Crop coordinates, in pixels */
- cropCoords = 2,
-
- /* Shadow color */
- shadowColor = 3,
-
- /* Shadow params */
- shadowParams = 4,
- };
-
- Program(const ProgramCache::Key& needs, const char* vertex, const char* fragment);
- ~Program();
-
- /* whether this object is usable */
- bool isValid() const;
-
- /* Binds this program to the GLES context */
- void use();
-
- /* Returns the location of the specified attribute */
- GLuint getAttrib(const char* name) const;
-
- /* Returns the location of the specified uniform */
- GLint getUniform(const char* name) const;
-
- /* set-up uniforms from the description */
- void setUniforms(const Description& desc);
-
-private:
- GLuint buildShader(const char* source, GLenum type);
-
- // whether the initialization succeeded
- bool mInitialized;
-
- // Name of the OpenGL program and shaders
- GLuint mProgram;
- GLuint mVertexShader;
- GLuint mFragmentShader;
-
- /* location of the projection matrix uniform */
- GLint mProjectionMatrixLoc;
-
- /* location of the texture matrix uniform */
- GLint mTextureMatrixLoc;
-
- /* location of the sampler uniform */
- GLint mSamplerLoc;
-
- /* location of the color uniform */
- GLint mColorLoc;
-
- /* location of display luminance uniform */
- GLint mDisplayMaxLuminanceLoc;
- /* location of max mastering luminance uniform */
- GLint mMaxMasteringLuminanceLoc;
- /* location of max content luminance uniform */
- GLint mMaxContentLuminanceLoc;
-
- /* location of transform matrix */
- GLint mInputTransformMatrixLoc;
- GLint mOutputTransformMatrixLoc;
- GLint mDisplayColorMatrixLoc;
-
- /* location of corner radius uniform */
- GLint mCornerRadiusLoc;
-
- /* location of surface crop origin uniform, for rounded corner clipping */
- GLint mCropCenterLoc;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
-
-#endif /* SF_RENDER_ENGINE_PROGRAM_H */
diff --git a/libs/renderengine/gl/ProgramCache.cpp b/libs/renderengine/gl/ProgramCache.cpp
deleted file mode 100644
index 422b070b05..0000000000
--- a/libs/renderengine/gl/ProgramCache.cpp
+++ /dev/null
@@ -1,830 +0,0 @@
-/*
- * Copyright 2013 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 ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "ProgramCache.h"
-
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-#include <log/log.h>
-#include <renderengine/private/Description.h>
-#include <utils/String8.h>
-#include <utils/Trace.h>
-#include "Program.h"
-
-ANDROID_SINGLETON_STATIC_INSTANCE(android::renderengine::gl::ProgramCache)
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-/*
- * A simple formatter class to automatically add the endl and
- * manage the indentation.
- */
-
-class Formatter;
-static Formatter& indent(Formatter& f);
-static Formatter& dedent(Formatter& f);
-
-class Formatter {
- String8 mString;
- int mIndent;
- typedef Formatter& (*FormaterManipFunc)(Formatter&);
- friend Formatter& indent(Formatter& f);
- friend Formatter& dedent(Formatter& f);
-
-public:
- Formatter() : mIndent(0) {}
-
- String8 getString() const { return mString; }
-
- friend Formatter& operator<<(Formatter& out, const char* in) {
- for (int i = 0; i < out.mIndent; i++) {
- out.mString.append(" ");
- }
- out.mString.append(in);
- out.mString.append("\n");
- return out;
- }
- friend inline Formatter& operator<<(Formatter& out, const String8& in) {
- return operator<<(out, in.c_str());
- }
- friend inline Formatter& operator<<(Formatter& to, FormaterManipFunc func) {
- return (*func)(to);
- }
-};
-Formatter& indent(Formatter& f) {
- f.mIndent++;
- return f;
-}
-Formatter& dedent(Formatter& f) {
- f.mIndent--;
- return f;
-}
-
-void ProgramCache::primeCache(
- EGLContext context, bool useColorManagement, bool toneMapperShaderOnly) {
- auto& cache = mCaches[context];
- uint32_t shaderCount = 0;
-
- if (toneMapperShaderOnly) {
- Key shaderKey;
- // base settings used by HDR->SDR tonemap only
- shaderKey.set(Key::BLEND_MASK | Key::INPUT_TRANSFORM_MATRIX_MASK |
- Key::OUTPUT_TRANSFORM_MATRIX_MASK | Key::OUTPUT_TF_MASK |
- Key::OPACITY_MASK | Key::ALPHA_MASK |
- Key::ROUNDED_CORNERS_MASK | Key::TEXTURE_MASK,
- Key::BLEND_NORMAL | Key::INPUT_TRANSFORM_MATRIX_ON |
- Key::OUTPUT_TRANSFORM_MATRIX_ON | Key::OUTPUT_TF_SRGB |
- Key::OPACITY_OPAQUE | Key::ALPHA_EQ_ONE |
- Key::ROUNDED_CORNERS_OFF | Key::TEXTURE_EXT);
- for (int i = 0; i < 4; i++) {
- // Cache input transfer for HLG & ST2084
- shaderKey.set(Key::INPUT_TF_MASK, (i & 1) ?
- Key::INPUT_TF_HLG : Key::INPUT_TF_ST2084);
-
- // Cache Y410 input on or off
- shaderKey.set(Key::Y410_BT2020_MASK, (i & 2) ?
- Key::Y410_BT2020_ON : Key::Y410_BT2020_OFF);
- if (cache.count(shaderKey) == 0) {
- cache.emplace(shaderKey, generateProgram(shaderKey));
- shaderCount++;
- }
- }
- return;
- }
-
- uint32_t keyMask = Key::BLEND_MASK | Key::OPACITY_MASK | Key::ALPHA_MASK | Key::TEXTURE_MASK
- | Key::ROUNDED_CORNERS_MASK;
- // Prime the cache for all combinations of the above masks,
- // leaving off the experimental color matrix mask options.
-
- nsecs_t timeBefore = systemTime();
- for (uint32_t keyVal = 0; keyVal <= keyMask; keyVal++) {
- Key shaderKey;
- shaderKey.set(keyMask, keyVal);
- uint32_t tex = shaderKey.getTextureTarget();
- if (tex != Key::TEXTURE_OFF && tex != Key::TEXTURE_EXT && tex != Key::TEXTURE_2D) {
- continue;
- }
- if (cache.count(shaderKey) == 0) {
- cache.emplace(shaderKey, generateProgram(shaderKey));
- shaderCount++;
- }
- }
-
- // Prime for sRGB->P3 conversion
- if (useColorManagement) {
- Key shaderKey;
- shaderKey.set(Key::BLEND_MASK | Key::OUTPUT_TRANSFORM_MATRIX_MASK | Key::INPUT_TF_MASK |
- Key::OUTPUT_TF_MASK,
- Key::BLEND_PREMULT | Key::OUTPUT_TRANSFORM_MATRIX_ON | Key::INPUT_TF_SRGB |
- Key::OUTPUT_TF_SRGB);
- for (int i = 0; i < 16; i++) {
- shaderKey.set(Key::OPACITY_MASK,
- (i & 1) ? Key::OPACITY_OPAQUE : Key::OPACITY_TRANSLUCENT);
- shaderKey.set(Key::ALPHA_MASK, (i & 2) ? Key::ALPHA_LT_ONE : Key::ALPHA_EQ_ONE);
-
- // Cache rounded corners
- shaderKey.set(Key::ROUNDED_CORNERS_MASK,
- (i & 4) ? Key::ROUNDED_CORNERS_ON : Key::ROUNDED_CORNERS_OFF);
-
- // Cache texture off option for window transition
- shaderKey.set(Key::TEXTURE_MASK, (i & 8) ? Key::TEXTURE_EXT : Key::TEXTURE_OFF);
- if (cache.count(shaderKey) == 0) {
- cache.emplace(shaderKey, generateProgram(shaderKey));
- shaderCount++;
- }
- }
- }
-
- nsecs_t timeAfter = systemTime();
- float compileTimeMs = static_cast<float>(timeAfter - timeBefore) / 1.0E6;
- ALOGD("shader cache generated - %u shaders in %f ms\n", shaderCount, compileTimeMs);
-}
-
-ProgramCache::Key ProgramCache::computeKey(const Description& description) {
- Key needs;
- needs.set(Key::TEXTURE_MASK,
- !description.textureEnabled
- ? Key::TEXTURE_OFF
- : description.texture.getTextureTarget() == GL_TEXTURE_EXTERNAL_OES
- ? Key::TEXTURE_EXT
- : description.texture.getTextureTarget() == GL_TEXTURE_2D
- ? Key::TEXTURE_2D
- : Key::TEXTURE_OFF)
- .set(Key::ALPHA_MASK, (description.color.a < 1) ? Key::ALPHA_LT_ONE : Key::ALPHA_EQ_ONE)
- .set(Key::BLEND_MASK,
- description.isPremultipliedAlpha ? Key::BLEND_PREMULT : Key::BLEND_NORMAL)
- .set(Key::OPACITY_MASK,
- description.isOpaque ? Key::OPACITY_OPAQUE : Key::OPACITY_TRANSLUCENT)
- .set(Key::Key::INPUT_TRANSFORM_MATRIX_MASK,
- description.hasInputTransformMatrix() ? Key::INPUT_TRANSFORM_MATRIX_ON
- : Key::INPUT_TRANSFORM_MATRIX_OFF)
- .set(Key::Key::OUTPUT_TRANSFORM_MATRIX_MASK,
- description.hasOutputTransformMatrix() || description.hasColorMatrix()
- ? Key::OUTPUT_TRANSFORM_MATRIX_ON
- : Key::OUTPUT_TRANSFORM_MATRIX_OFF)
- .set(Key::Key::DISPLAY_COLOR_TRANSFORM_MATRIX_MASK,
- description.hasDisplayColorMatrix() ? Key::DISPLAY_COLOR_TRANSFORM_MATRIX_ON
- : Key::DISPLAY_COLOR_TRANSFORM_MATRIX_OFF)
- .set(Key::ROUNDED_CORNERS_MASK,
- description.cornerRadius > 0 ? Key::ROUNDED_CORNERS_ON : Key::ROUNDED_CORNERS_OFF)
- .set(Key::SHADOW_MASK, description.drawShadows ? Key::SHADOW_ON : Key::SHADOW_OFF);
- needs.set(Key::Y410_BT2020_MASK,
- description.isY410BT2020 ? Key::Y410_BT2020_ON : Key::Y410_BT2020_OFF);
-
- if (needs.hasTransformMatrix() ||
- (description.inputTransferFunction != description.outputTransferFunction)) {
- switch (description.inputTransferFunction) {
- case Description::TransferFunction::LINEAR:
- default:
- needs.set(Key::INPUT_TF_MASK, Key::INPUT_TF_LINEAR);
- break;
- case Description::TransferFunction::SRGB:
- needs.set(Key::INPUT_TF_MASK, Key::INPUT_TF_SRGB);
- break;
- case Description::TransferFunction::ST2084:
- needs.set(Key::INPUT_TF_MASK, Key::INPUT_TF_ST2084);
- break;
- case Description::TransferFunction::HLG:
- needs.set(Key::INPUT_TF_MASK, Key::INPUT_TF_HLG);
- break;
- }
-
- switch (description.outputTransferFunction) {
- case Description::TransferFunction::LINEAR:
- default:
- needs.set(Key::OUTPUT_TF_MASK, Key::OUTPUT_TF_LINEAR);
- break;
- case Description::TransferFunction::SRGB:
- needs.set(Key::OUTPUT_TF_MASK, Key::OUTPUT_TF_SRGB);
- break;
- case Description::TransferFunction::ST2084:
- needs.set(Key::OUTPUT_TF_MASK, Key::OUTPUT_TF_ST2084);
- break;
- case Description::TransferFunction::HLG:
- needs.set(Key::OUTPUT_TF_MASK, Key::OUTPUT_TF_HLG);
- break;
- }
- }
-
- return needs;
-}
-
-// Generate EOTF that converts signal values to relative display light,
-// both normalized to [0, 1].
-void ProgramCache::generateEOTF(Formatter& fs, const Key& needs) {
- switch (needs.getInputTF()) {
- case Key::INPUT_TF_SRGB:
- fs << R"__SHADER__(
- float EOTF_sRGB(float srgb) {
- return srgb <= 0.04045 ? srgb / 12.92 : pow((srgb + 0.055) / 1.055, 2.4);
- }
-
- vec3 EOTF_sRGB(const vec3 srgb) {
- return vec3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b));
- }
-
- vec3 EOTF(const vec3 srgb) {
- return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb));
- }
- )__SHADER__";
- break;
- case Key::INPUT_TF_ST2084:
- fs << R"__SHADER__(
- vec3 EOTF(const highp vec3 color) {
- const highp float m1 = (2610.0 / 4096.0) / 4.0;
- const highp float m2 = (2523.0 / 4096.0) * 128.0;
- const highp float c1 = (3424.0 / 4096.0);
- const highp float c2 = (2413.0 / 4096.0) * 32.0;
- const highp float c3 = (2392.0 / 4096.0) * 32.0;
-
- highp vec3 tmp = pow(clamp(color, 0.0, 1.0), 1.0 / vec3(m2));
- tmp = max(tmp - c1, 0.0) / (c2 - c3 * tmp);
- return pow(tmp, 1.0 / vec3(m1));
- }
- )__SHADER__";
- break;
- case Key::INPUT_TF_HLG:
- fs << R"__SHADER__(
- highp float EOTF_channel(const highp float channel) {
- const highp float a = 0.17883277;
- const highp float b = 0.28466892;
- const highp float c = 0.55991073;
- return channel <= 0.5 ? channel * channel / 3.0 :
- (exp((channel - c) / a) + b) / 12.0;
- }
-
- vec3 EOTF(const highp vec3 color) {
- return vec3(EOTF_channel(color.r), EOTF_channel(color.g),
- EOTF_channel(color.b));
- }
- )__SHADER__";
- break;
- default:
- fs << R"__SHADER__(
- vec3 EOTF(const vec3 linear) {
- return linear;
- }
- )__SHADER__";
- break;
- }
-}
-
-void ProgramCache::generateToneMappingProcess(Formatter& fs, const Key& needs) {
- // Convert relative light to absolute light.
- switch (needs.getInputTF()) {
- case Key::INPUT_TF_ST2084:
- fs << R"__SHADER__(
- highp vec3 ScaleLuminance(highp vec3 color) {
- return color * 10000.0;
- }
- )__SHADER__";
- break;
- case Key::INPUT_TF_HLG:
- fs << R"__SHADER__(
- highp vec3 ScaleLuminance(highp vec3 color) {
- // The formula is:
- // alpha * pow(Y, gamma - 1.0) * color + beta;
- // where alpha is 1000.0, gamma is 1.2, beta is 0.0.
- return color * 1000.0 * pow(color.y, 0.2);
- }
- )__SHADER__";
- break;
- default:
- fs << R"__SHADER__(
- highp vec3 ScaleLuminance(highp vec3 color) {
- return color * displayMaxLuminance;
- }
- )__SHADER__";
- break;
- }
-
- // Tone map absolute light to display luminance range.
- switch (needs.getInputTF()) {
- case Key::INPUT_TF_ST2084:
- case Key::INPUT_TF_HLG:
- switch (needs.getOutputTF()) {
- case Key::OUTPUT_TF_HLG:
- // Right now when mixed PQ and HLG contents are presented,
- // HLG content will always be converted to PQ. However, for
- // completeness, we simply clamp the value to [0.0, 1000.0].
- fs << R"__SHADER__(
- highp vec3 ToneMap(highp vec3 color) {
- return clamp(color, 0.0, 1000.0);
- }
- )__SHADER__";
- break;
- case Key::OUTPUT_TF_ST2084:
- fs << R"__SHADER__(
- highp vec3 ToneMap(highp vec3 color) {
- return color;
- }
- )__SHADER__";
- break;
- default:
- fs << R"__SHADER__(
- highp vec3 ToneMap(highp vec3 color) {
- float maxMasteringLumi = maxMasteringLuminance;
- float maxContentLumi = maxContentLuminance;
- float maxInLumi = min(maxMasteringLumi, maxContentLumi);
- float maxOutLumi = displayMaxLuminance;
-
- float nits = color.y;
-
- // clamp to max input luminance
- nits = clamp(nits, 0.0, maxInLumi);
-
- // scale [0.0, maxInLumi] to [0.0, maxOutLumi]
- if (maxInLumi <= maxOutLumi) {
- return color * (maxOutLumi / maxInLumi);
- } else {
- // three control points
- const float x0 = 10.0;
- const float y0 = 17.0;
- float x1 = maxOutLumi * 0.75;
- float y1 = x1;
- float x2 = x1 + (maxInLumi - x1) / 2.0;
- float y2 = y1 + (maxOutLumi - y1) * 0.75;
-
- // horizontal distances between the last three control points
- float h12 = x2 - x1;
- float h23 = maxInLumi - x2;
- // tangents at the last three control points
- float m1 = (y2 - y1) / h12;
- float m3 = (maxOutLumi - y2) / h23;
- float m2 = (m1 + m3) / 2.0;
-
- if (nits < x0) {
- // scale [0.0, x0] to [0.0, y0] linearly
- float slope = y0 / x0;
- return color * slope;
- } else if (nits < x1) {
- // scale [x0, x1] to [y0, y1] linearly
- float slope = (y1 - y0) / (x1 - x0);
- nits = y0 + (nits - x0) * slope;
- } else if (nits < x2) {
- // scale [x1, x2] to [y1, y2] using Hermite interp
- float t = (nits - x1) / h12;
- nits = (y1 * (1.0 + 2.0 * t) + h12 * m1 * t) * (1.0 - t) * (1.0 - t) +
- (y2 * (3.0 - 2.0 * t) + h12 * m2 * (t - 1.0)) * t * t;
- } else {
- // scale [x2, maxInLumi] to [y2, maxOutLumi] using Hermite interp
- float t = (nits - x2) / h23;
- nits = (y2 * (1.0 + 2.0 * t) + h23 * m2 * t) * (1.0 - t) * (1.0 - t) +
- (maxOutLumi * (3.0 - 2.0 * t) + h23 * m3 * (t - 1.0)) * t * t;
- }
- }
-
- // color.y is greater than x0 and is thus non-zero
- return color * (nits / color.y);
- }
- )__SHADER__";
- break;
- }
- break;
- default:
- // inverse tone map; the output luminance can be up to maxOutLumi.
- fs << R"__SHADER__(
- highp vec3 ToneMap(highp vec3 color) {
- const float maxOutLumi = 3000.0;
-
- const float x0 = 5.0;
- const float y0 = 2.5;
- float x1 = displayMaxLuminance * 0.7;
- float y1 = maxOutLumi * 0.15;
- float x2 = displayMaxLuminance * 0.9;
- float y2 = maxOutLumi * 0.45;
- float x3 = displayMaxLuminance;
- float y3 = maxOutLumi;
-
- float c1 = y1 / 3.0;
- float c2 = y2 / 2.0;
- float c3 = y3 / 1.5;
-
- float nits = color.y;
-
- float scale;
- if (nits <= x0) {
- // scale [0.0, x0] to [0.0, y0] linearly
- const float slope = y0 / x0;
- return color * slope;
- } else if (nits <= x1) {
- // scale [x0, x1] to [y0, y1] using a curve
- float t = (nits - x0) / (x1 - x0);
- nits = (1.0 - t) * (1.0 - t) * y0 + 2.0 * (1.0 - t) * t * c1 + t * t * y1;
- } else if (nits <= x2) {
- // scale [x1, x2] to [y1, y2] using a curve
- float t = (nits - x1) / (x2 - x1);
- nits = (1.0 - t) * (1.0 - t) * y1 + 2.0 * (1.0 - t) * t * c2 + t * t * y2;
- } else {
- // scale [x2, x3] to [y2, y3] using a curve
- float t = (nits - x2) / (x3 - x2);
- nits = (1.0 - t) * (1.0 - t) * y2 + 2.0 * (1.0 - t) * t * c3 + t * t * y3;
- }
-
- // color.y is greater than x0 and is thus non-zero
- return color * (nits / color.y);
- }
- )__SHADER__";
- break;
- }
-
- // convert absolute light to relative light.
- switch (needs.getOutputTF()) {
- case Key::OUTPUT_TF_ST2084:
- fs << R"__SHADER__(
- highp vec3 NormalizeLuminance(highp vec3 color) {
- return color / 10000.0;
- }
- )__SHADER__";
- break;
- case Key::OUTPUT_TF_HLG:
- fs << R"__SHADER__(
- highp vec3 NormalizeLuminance(highp vec3 color) {
- return color / 1000.0 * pow(color.y / 1000.0, -0.2 / 1.2);
- }
- )__SHADER__";
- break;
- default:
- fs << R"__SHADER__(
- highp vec3 NormalizeLuminance(highp vec3 color) {
- return color / displayMaxLuminance;
- }
- )__SHADER__";
- break;
- }
-}
-
-// Generate OOTF that modifies the relative scence light to relative display light.
-void ProgramCache::generateOOTF(Formatter& fs, const ProgramCache::Key& needs) {
- if (!needs.needsToneMapping()) {
- fs << R"__SHADER__(
- highp vec3 OOTF(const highp vec3 color) {
- return color;
- }
- )__SHADER__";
- } else {
- generateToneMappingProcess(fs, needs);
- fs << R"__SHADER__(
- highp vec3 OOTF(const highp vec3 color) {
- return NormalizeLuminance(ToneMap(ScaleLuminance(color)));
- }
- )__SHADER__";
- }
-}
-
-// Generate OETF that converts relative display light to signal values,
-// both normalized to [0, 1]
-void ProgramCache::generateOETF(Formatter& fs, const Key& needs) {
- switch (needs.getOutputTF()) {
- case Key::OUTPUT_TF_SRGB:
- fs << R"__SHADER__(
- float OETF_sRGB(const float linear) {
- return linear <= 0.0031308 ?
- linear * 12.92 : (pow(linear, 1.0 / 2.4) * 1.055) - 0.055;
- }
-
- vec3 OETF_sRGB(const vec3 linear) {
- return vec3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b));
- }
-
- vec3 OETF(const vec3 linear) {
- return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb));
- }
- )__SHADER__";
- break;
- case Key::OUTPUT_TF_ST2084:
- fs << R"__SHADER__(
- vec3 OETF(const vec3 linear) {
- const highp float m1 = (2610.0 / 4096.0) / 4.0;
- const highp float m2 = (2523.0 / 4096.0) * 128.0;
- const highp float c1 = (3424.0 / 4096.0);
- const highp float c2 = (2413.0 / 4096.0) * 32.0;
- const highp float c3 = (2392.0 / 4096.0) * 32.0;
-
- highp vec3 tmp = pow(linear, vec3(m1));
- tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
- return pow(tmp, vec3(m2));
- }
- )__SHADER__";
- break;
- case Key::OUTPUT_TF_HLG:
- fs << R"__SHADER__(
- highp float OETF_channel(const highp float channel) {
- const highp float a = 0.17883277;
- const highp float b = 0.28466892;
- const highp float c = 0.55991073;
- return channel <= 1.0 / 12.0 ? sqrt(3.0 * channel) :
- a * log(12.0 * channel - b) + c;
- }
-
- vec3 OETF(const highp vec3 color) {
- return vec3(OETF_channel(color.r), OETF_channel(color.g),
- OETF_channel(color.b));
- }
- )__SHADER__";
- break;
- default:
- fs << R"__SHADER__(
- vec3 OETF(const vec3 linear) {
- return linear;
- }
- )__SHADER__";
- break;
- }
-}
-
-String8 ProgramCache::generateVertexShader(const Key& needs) {
- Formatter vs;
- if (needs.hasTextureCoords()) {
- vs << "attribute vec4 texCoords;"
- << "varying vec2 outTexCoords;";
- }
- if (needs.hasRoundedCorners()) {
- vs << "attribute lowp vec4 cropCoords;";
- vs << "varying lowp vec2 outCropCoords;";
- }
- if (needs.drawShadows()) {
- vs << "attribute lowp vec4 shadowColor;";
- vs << "varying lowp vec4 outShadowColor;";
- vs << "attribute lowp vec4 shadowParams;";
- vs << "varying lowp vec3 outShadowParams;";
- }
- vs << "attribute vec4 position;"
- << "uniform mat4 projection;"
- << "uniform mat4 texture;"
- << "void main(void) {" << indent << "gl_Position = projection * position;";
- if (needs.hasTextureCoords()) {
- vs << "outTexCoords = (texture * texCoords).st;";
- }
- if (needs.hasRoundedCorners()) {
- vs << "outCropCoords = cropCoords.st;";
- }
- if (needs.drawShadows()) {
- vs << "outShadowColor = shadowColor;";
- vs << "outShadowParams = shadowParams.xyz;";
- }
- vs << dedent << "}";
- return vs.getString();
-}
-
-String8 ProgramCache::generateFragmentShader(const Key& needs) {
- Formatter fs;
- if (needs.getTextureTarget() == Key::TEXTURE_EXT) {
- fs << "#extension GL_OES_EGL_image_external : require";
- }
-
- // default precision is required-ish in fragment shaders
- fs << "precision mediump float;";
-
- if (needs.getTextureTarget() == Key::TEXTURE_EXT) {
- fs << "uniform samplerExternalOES sampler;";
- } else if (needs.getTextureTarget() == Key::TEXTURE_2D) {
- fs << "uniform sampler2D sampler;";
- }
-
- if (needs.hasTextureCoords()) {
- fs << "varying highp vec2 outTexCoords;";
- }
-
- if (needs.hasRoundedCorners()) {
- // Rounded corners implementation using a signed distance function.
- fs << R"__SHADER__(
- uniform float cornerRadius;
- uniform vec2 cropCenter;
- varying vec2 outCropCoords;
-
- /**
- * This function takes the current crop coordinates and calculates an alpha value based
- * on the corner radius and distance from the crop center.
- */
- float applyCornerRadius(vec2 cropCoords)
- {
- vec2 position = cropCoords - cropCenter;
- // Scale down the dist vector here, as otherwise large corner
- // radii can cause floating point issues when computing the norm
- vec2 dist = (abs(position) - cropCenter + vec2(cornerRadius)) / 16.0;
- // Once we've found the norm, then scale back up.
- float plane = length(max(dist, vec2(0.0))) * 16.0;
- return 1.0 - clamp(plane - cornerRadius, 0.0, 1.0);
- }
- )__SHADER__";
- }
-
- if (needs.drawShadows()) {
- fs << R"__SHADER__(
- varying lowp vec4 outShadowColor;
- varying lowp vec3 outShadowParams;
-
- /**
- * Returns the shadow color.
- */
- vec4 getShadowColor()
- {
- lowp float d = length(outShadowParams.xy);
- vec2 uv = vec2(outShadowParams.z * (1.0 - d), 0.5);
- lowp float factor = texture2D(sampler, uv).a;
- return outShadowColor * factor;
- }
- )__SHADER__";
- }
-
- if (needs.getTextureTarget() == Key::TEXTURE_OFF || needs.hasAlpha()) {
- fs << "uniform vec4 color;";
- }
-
- if (needs.isY410BT2020()) {
- fs << R"__SHADER__(
- vec3 convertY410BT2020(const vec3 color) {
- const vec3 offset = vec3(0.0625, 0.5, 0.5);
- const mat3 transform = mat3(
- vec3(1.1678, 1.1678, 1.1678),
- vec3( 0.0, -0.1878, 2.1481),
- vec3(1.6836, -0.6523, 0.0));
- // Y is in G, U is in R, and V is in B
- return clamp(transform * (color.grb - offset), 0.0, 1.0);
- }
- )__SHADER__";
- }
-
- if (needs.hasTransformMatrix() || (needs.getInputTF() != needs.getOutputTF()) ||
- needs.hasDisplayColorMatrix()) {
- if (needs.needsToneMapping()) {
- fs << "uniform float displayMaxLuminance;";
- fs << "uniform float maxMasteringLuminance;";
- fs << "uniform float maxContentLuminance;";
- }
-
- if (needs.hasInputTransformMatrix()) {
- fs << "uniform mat4 inputTransformMatrix;";
- fs << R"__SHADER__(
- highp vec3 InputTransform(const highp vec3 color) {
- return clamp(vec3(inputTransformMatrix * vec4(color, 1.0)), 0.0, 1.0);
- }
- )__SHADER__";
- } else {
- fs << R"__SHADER__(
- highp vec3 InputTransform(const highp vec3 color) {
- return color;
- }
- )__SHADER__";
- }
-
- // the transformation from a wider colorspace to a narrower one can
- // result in >1.0 or <0.0 pixel values
- if (needs.hasOutputTransformMatrix()) {
- fs << "uniform mat4 outputTransformMatrix;";
- fs << R"__SHADER__(
- highp vec3 OutputTransform(const highp vec3 color) {
- return clamp(vec3(outputTransformMatrix * vec4(color, 1.0)), 0.0, 1.0);
- }
- )__SHADER__";
- } else {
- fs << R"__SHADER__(
- highp vec3 OutputTransform(const highp vec3 color) {
- return clamp(color, 0.0, 1.0);
- }
- )__SHADER__";
- }
-
- if (needs.hasDisplayColorMatrix()) {
- fs << "uniform mat4 displayColorMatrix;";
- fs << R"__SHADER__(
- highp vec3 DisplayColorMatrix(const highp vec3 color) {
- return clamp(vec3(displayColorMatrix * vec4(color, 1.0)), 0.0, 1.0);
- }
- )__SHADER__";
- } else {
- fs << R"__SHADER__(
- highp vec3 DisplayColorMatrix(const highp vec3 color) {
- return color;
- }
- )__SHADER__";
- }
-
- generateEOTF(fs, needs);
- generateOOTF(fs, needs);
- generateOETF(fs, needs);
- }
-
- fs << "void main(void) {" << indent;
- if (needs.drawShadows()) {
- fs << "gl_FragColor = getShadowColor();";
- } else {
- if (needs.isTexturing()) {
- fs << "gl_FragColor = texture2D(sampler, outTexCoords);";
- if (needs.isY410BT2020()) {
- fs << "gl_FragColor.rgb = convertY410BT2020(gl_FragColor.rgb);";
- }
- } else {
- fs << "gl_FragColor.rgb = color.rgb;";
- fs << "gl_FragColor.a = 1.0;";
- }
- if (needs.isOpaque()) {
- fs << "gl_FragColor.a = 1.0;";
- }
- }
-
- if (needs.hasTransformMatrix() || (needs.getInputTF() != needs.getOutputTF()) ||
- needs.hasDisplayColorMatrix()) {
- if (!needs.isOpaque() && needs.isPremultiplied()) {
- // un-premultiply if needed before linearization
- // avoid divide by 0 by adding 0.5/256 to the alpha channel
- fs << "gl_FragColor.rgb = gl_FragColor.rgb / (gl_FragColor.a + 0.0019);";
- }
- fs << "gl_FragColor.rgb = "
- "DisplayColorMatrix(OETF(OutputTransform(OOTF(InputTransform(EOTF(gl_FragColor.rgb)))"
- ")));";
-
- if (!needs.isOpaque() && needs.isPremultiplied()) {
- // and re-premultiply if needed after gamma correction
- fs << "gl_FragColor.rgb = gl_FragColor.rgb * (gl_FragColor.a + 0.0019);";
- }
- }
-
- /*
- * Whether applying layer alpha before or after color transform doesn't matter,
- * as long as we can undo premultiplication. But we cannot un-premultiply
- * for color transform if the layer alpha = 0, e.g. 0 / (0 + 0.0019) = 0.
- */
- if (!needs.drawShadows()) {
- if (needs.hasAlpha()) {
- // modulate the current alpha value with alpha set
- if (needs.isPremultiplied()) {
- // ... and the color too if we're premultiplied
- fs << "gl_FragColor *= color.a;";
- } else {
- fs << "gl_FragColor.a *= color.a;";
- }
- }
- }
-
- if (needs.hasRoundedCorners()) {
- if (needs.isPremultiplied()) {
- fs << "gl_FragColor *= vec4(applyCornerRadius(outCropCoords));";
- } else {
- fs << "gl_FragColor.a *= applyCornerRadius(outCropCoords);";
- }
- }
-
- fs << dedent << "}";
- return fs.getString();
-}
-
-std::unique_ptr<Program> ProgramCache::generateProgram(const Key& needs) {
- ATRACE_CALL();
-
- // vertex shader
- String8 vs = generateVertexShader(needs);
-
- // fragment shader
- String8 fs = generateFragmentShader(needs);
-
- return std::make_unique<Program>(needs, vs.c_str(), fs.c_str());
-}
-
-void ProgramCache::useProgram(EGLContext context, const Description& description) {
- // generate the key for the shader based on the description
- Key needs(computeKey(description));
-
- // look-up the program in the cache
- auto& cache = mCaches[context];
- auto it = cache.find(needs);
- if (it == cache.end()) {
- // we didn't find our program, so generate one...
- nsecs_t time = systemTime();
- it = cache.emplace(needs, generateProgram(needs)).first;
- time = systemTime() - time;
-
- ALOGV(">>> generated new program for context %p: needs=%08X, time=%u ms (%zu programs)",
- context, needs.mKey, uint32_t(ns2ms(time)), cache.size());
- }
-
- // here we have a suitable program for this description
- std::unique_ptr<Program>& program = it->second;
- if (program->isValid()) {
- program->use();
- program->setUniforms(description);
- }
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/ProgramCache.h b/libs/renderengine/gl/ProgramCache.h
deleted file mode 100644
index 535d21cd52..0000000000
--- a/libs/renderengine/gl/ProgramCache.h
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright 2013 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.
- */
-
-#ifndef SF_RENDER_ENGINE_PROGRAMCACHE_H
-#define SF_RENDER_ENGINE_PROGRAMCACHE_H
-
-#include <memory>
-#include <unordered_map>
-
-#include <EGL/egl.h>
-#include <GLES2/gl2.h>
-#include <renderengine/private/Description.h>
-#include <utils/Singleton.h>
-#include <utils/TypeHelpers.h>
-
-namespace android {
-
-class String8;
-
-namespace renderengine {
-
-struct Description;
-
-namespace gl {
-
-class Formatter;
-class Program;
-
-/*
- * This class generates GLSL programs suitable to handle a given
- * Description. It's responsible for figuring out what to
- * generate from a Description.
- * It also maintains a cache of these Programs.
- */
-class ProgramCache : public Singleton<ProgramCache> {
-public:
- /*
- * Key is used to retrieve a Program in the cache.
- * A Key is generated from a Description.
- */
- class Key {
- friend class ProgramCache;
- typedef uint32_t key_t;
- key_t mKey;
-
- public:
- enum {
- BLEND_SHIFT = 0,
- BLEND_MASK = 1 << BLEND_SHIFT,
- BLEND_PREMULT = 1 << BLEND_SHIFT,
- BLEND_NORMAL = 0 << BLEND_SHIFT,
-
- OPACITY_SHIFT = 1,
- OPACITY_MASK = 1 << OPACITY_SHIFT,
- OPACITY_OPAQUE = 1 << OPACITY_SHIFT,
- OPACITY_TRANSLUCENT = 0 << OPACITY_SHIFT,
-
- ALPHA_SHIFT = 2,
- ALPHA_MASK = 1 << ALPHA_SHIFT,
- ALPHA_LT_ONE = 1 << ALPHA_SHIFT,
- ALPHA_EQ_ONE = 0 << ALPHA_SHIFT,
-
- TEXTURE_SHIFT = 3,
- TEXTURE_MASK = 3 << TEXTURE_SHIFT,
- TEXTURE_OFF = 0 << TEXTURE_SHIFT,
- TEXTURE_EXT = 1 << TEXTURE_SHIFT,
- TEXTURE_2D = 2 << TEXTURE_SHIFT,
-
- ROUNDED_CORNERS_SHIFT = 5,
- ROUNDED_CORNERS_MASK = 1 << ROUNDED_CORNERS_SHIFT,
- ROUNDED_CORNERS_OFF = 0 << ROUNDED_CORNERS_SHIFT,
- ROUNDED_CORNERS_ON = 1 << ROUNDED_CORNERS_SHIFT,
-
- INPUT_TRANSFORM_MATRIX_SHIFT = 6,
- INPUT_TRANSFORM_MATRIX_MASK = 1 << INPUT_TRANSFORM_MATRIX_SHIFT,
- INPUT_TRANSFORM_MATRIX_OFF = 0 << INPUT_TRANSFORM_MATRIX_SHIFT,
- INPUT_TRANSFORM_MATRIX_ON = 1 << INPUT_TRANSFORM_MATRIX_SHIFT,
-
- OUTPUT_TRANSFORM_MATRIX_SHIFT = 7,
- OUTPUT_TRANSFORM_MATRIX_MASK = 1 << OUTPUT_TRANSFORM_MATRIX_SHIFT,
- OUTPUT_TRANSFORM_MATRIX_OFF = 0 << OUTPUT_TRANSFORM_MATRIX_SHIFT,
- OUTPUT_TRANSFORM_MATRIX_ON = 1 << OUTPUT_TRANSFORM_MATRIX_SHIFT,
-
- INPUT_TF_SHIFT = 8,
- INPUT_TF_MASK = 3 << INPUT_TF_SHIFT,
- INPUT_TF_LINEAR = 0 << INPUT_TF_SHIFT,
- INPUT_TF_SRGB = 1 << INPUT_TF_SHIFT,
- INPUT_TF_ST2084 = 2 << INPUT_TF_SHIFT,
- INPUT_TF_HLG = 3 << INPUT_TF_SHIFT,
-
- OUTPUT_TF_SHIFT = 10,
- OUTPUT_TF_MASK = 3 << OUTPUT_TF_SHIFT,
- OUTPUT_TF_LINEAR = 0 << OUTPUT_TF_SHIFT,
- OUTPUT_TF_SRGB = 1 << OUTPUT_TF_SHIFT,
- OUTPUT_TF_ST2084 = 2 << OUTPUT_TF_SHIFT,
- OUTPUT_TF_HLG = 3 << OUTPUT_TF_SHIFT,
-
- Y410_BT2020_SHIFT = 12,
- Y410_BT2020_MASK = 1 << Y410_BT2020_SHIFT,
- Y410_BT2020_OFF = 0 << Y410_BT2020_SHIFT,
- Y410_BT2020_ON = 1 << Y410_BT2020_SHIFT,
-
- SHADOW_SHIFT = 13,
- SHADOW_MASK = 1 << SHADOW_SHIFT,
- SHADOW_OFF = 0 << SHADOW_SHIFT,
- SHADOW_ON = 1 << SHADOW_SHIFT,
-
- DISPLAY_COLOR_TRANSFORM_MATRIX_SHIFT = 14,
- DISPLAY_COLOR_TRANSFORM_MATRIX_MASK = 1 << DISPLAY_COLOR_TRANSFORM_MATRIX_SHIFT,
- DISPLAY_COLOR_TRANSFORM_MATRIX_OFF = 0 << DISPLAY_COLOR_TRANSFORM_MATRIX_SHIFT,
- DISPLAY_COLOR_TRANSFORM_MATRIX_ON = 1 << DISPLAY_COLOR_TRANSFORM_MATRIX_SHIFT,
- };
-
- inline Key() : mKey(0) {}
- inline Key(const Key& rhs) : mKey(rhs.mKey) {}
-
- inline Key& set(key_t mask, key_t value) {
- mKey = (mKey & ~mask) | value;
- return *this;
- }
-
- inline bool isTexturing() const { return (mKey & TEXTURE_MASK) != TEXTURE_OFF; }
- inline bool hasTextureCoords() const { return isTexturing() && !drawShadows(); }
- inline int getTextureTarget() const { return (mKey & TEXTURE_MASK); }
- inline bool isPremultiplied() const { return (mKey & BLEND_MASK) == BLEND_PREMULT; }
- inline bool isOpaque() const { return (mKey & OPACITY_MASK) == OPACITY_OPAQUE; }
- inline bool hasAlpha() const { return (mKey & ALPHA_MASK) == ALPHA_LT_ONE; }
- inline bool hasRoundedCorners() const {
- return (mKey & ROUNDED_CORNERS_MASK) == ROUNDED_CORNERS_ON;
- }
- inline bool drawShadows() const { return (mKey & SHADOW_MASK) == SHADOW_ON; }
- inline bool hasInputTransformMatrix() const {
- return (mKey & INPUT_TRANSFORM_MATRIX_MASK) == INPUT_TRANSFORM_MATRIX_ON;
- }
- inline bool hasOutputTransformMatrix() const {
- return (mKey & OUTPUT_TRANSFORM_MATRIX_MASK) == OUTPUT_TRANSFORM_MATRIX_ON;
- }
- inline bool hasDisplayColorMatrix() const {
- return (mKey & DISPLAY_COLOR_TRANSFORM_MATRIX_MASK) ==
- DISPLAY_COLOR_TRANSFORM_MATRIX_ON;
- }
- inline bool hasTransformMatrix() const {
- return hasInputTransformMatrix() || hasOutputTransformMatrix();
- }
- inline int getInputTF() const { return (mKey & INPUT_TF_MASK); }
- inline int getOutputTF() const { return (mKey & OUTPUT_TF_MASK); }
-
- // When HDR and non-HDR contents are mixed, or different types of HDR contents are
- // mixed, we will do a tone mapping process to tone map the input content to output
- // content. Currently, the following conversions handled, they are:
- // * SDR -> HLG
- // * SDR -> PQ
- // * HLG -> PQ
- inline bool needsToneMapping() const {
- int inputTF = getInputTF();
- int outputTF = getOutputTF();
-
- // Return false when converting from SDR to SDR.
- if (inputTF == Key::INPUT_TF_SRGB && outputTF == Key::OUTPUT_TF_LINEAR) {
- return false;
- }
- if (inputTF == Key::INPUT_TF_LINEAR && outputTF == Key::OUTPUT_TF_SRGB) {
- return false;
- }
-
- inputTF >>= Key::INPUT_TF_SHIFT;
- outputTF >>= Key::OUTPUT_TF_SHIFT;
- return inputTF != outputTF;
- }
- inline bool isY410BT2020() const { return (mKey & Y410_BT2020_MASK) == Y410_BT2020_ON; }
-
- // for use by std::unordered_map
-
- bool operator==(const Key& other) const { return mKey == other.mKey; }
-
- struct Hash {
- size_t operator()(const Key& key) const { return static_cast<size_t>(key.mKey); }
- };
- };
-
- ProgramCache() = default;
- ~ProgramCache() = default;
-
- // Generate shaders to populate the cache
- void primeCache(const EGLContext context, bool useColorManagement, bool toneMapperShaderOnly);
-
- size_t getSize(const EGLContext context) { return mCaches[context].size(); }
-
- // useProgram lookup a suitable program in the cache or generates one
- // if none can be found.
- void useProgram(const EGLContext context, const Description& description);
-
- void purgeCaches() { mCaches.clear(); }
-
-private:
- // compute a cache Key from a Description
- static Key computeKey(const Description& description);
- // Generate EOTF based from Key.
- static void generateEOTF(Formatter& fs, const Key& needs);
- // Generate necessary tone mapping methods for OOTF.
- static void generateToneMappingProcess(Formatter& fs, const Key& needs);
- // Generate OOTF based from Key.
- static void generateOOTF(Formatter& fs, const Key& needs);
- // Generate OETF based from Key.
- static void generateOETF(Formatter& fs, const Key& needs);
- // generates a program from the Key
- static std::unique_ptr<Program> generateProgram(const Key& needs);
- // generates the vertex shader from the Key
- static String8 generateVertexShader(const Key& needs);
- // generates the fragment shader from the Key
- static String8 generateFragmentShader(const Key& needs);
-
- // Key/Value map used for caching Programs. Currently the cache
- // is never shrunk (and the GL program objects are never deleted).
- std::unordered_map<EGLContext, std::unordered_map<Key, std::unique_ptr<Program>, Key::Hash>>
- mCaches;
-};
-
-} // namespace gl
-} // namespace renderengine
-
-ANDROID_BASIC_TYPES_TRAITS(renderengine::gl::ProgramCache::Key)
-
-} // namespace android
-
-#endif /* SF_RENDER_ENGINE_PROGRAMCACHE_H */
diff --git a/libs/renderengine/gl/filters/BlurFilter.cpp b/libs/renderengine/gl/filters/BlurFilter.cpp
deleted file mode 100644
index 3455e08cfe..0000000000
--- a/libs/renderengine/gl/filters/BlurFilter.cpp
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Copyright 2019 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 ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "BlurFilter.h"
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <GLES3/gl3.h>
-#include <GLES3/gl3ext.h>
-#include <ui/GraphicTypes.h>
-#include <cstdint>
-
-#include <utils/Trace.h>
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-BlurFilter::BlurFilter(GLESRenderEngine& engine)
- : mEngine(engine),
- mCompositionFbo(engine),
- mPingFbo(engine),
- mPongFbo(engine),
- mMixProgram(engine),
- mBlurProgram(engine) {
- mMixProgram.compile(getVertexShader(), getMixFragShader());
- mMPosLoc = mMixProgram.getAttributeLocation("aPosition");
- mMUvLoc = mMixProgram.getAttributeLocation("aUV");
- mMTextureLoc = mMixProgram.getUniformLocation("uTexture");
- mMCompositionTextureLoc = mMixProgram.getUniformLocation("uCompositionTexture");
- mMMixLoc = mMixProgram.getUniformLocation("uMix");
-
- mBlurProgram.compile(getVertexShader(), getFragmentShader());
- mBPosLoc = mBlurProgram.getAttributeLocation("aPosition");
- mBUvLoc = mBlurProgram.getAttributeLocation("aUV");
- mBTextureLoc = mBlurProgram.getUniformLocation("uTexture");
- mBOffsetLoc = mBlurProgram.getUniformLocation("uOffset");
-
- static constexpr auto size = 2.0f;
- static constexpr auto translation = 1.0f;
- const GLfloat vboData[] = {
- // Vertex data
- translation - size, -translation - size,
- translation - size, -translation + size,
- translation + size, -translation + size,
- // UV data
- 0.0f, 0.0f - translation,
- 0.0f, size - translation,
- size, size - translation
- };
- mMeshBuffer.allocateBuffers(vboData, 12 /* size */);
-}
-
-status_t BlurFilter::setAsDrawTarget(const DisplaySettings& display, uint32_t radius) {
- ATRACE_NAME("BlurFilter::setAsDrawTarget");
- mRadius = radius;
- mDisplayX = display.physicalDisplay.left;
- mDisplayY = display.physicalDisplay.top;
-
- if (mDisplayWidth < display.physicalDisplay.width() ||
- mDisplayHeight < display.physicalDisplay.height()) {
- ATRACE_NAME("BlurFilter::allocatingTextures");
-
- mDisplayWidth = display.physicalDisplay.width();
- mDisplayHeight = display.physicalDisplay.height();
- mCompositionFbo.allocateBuffers(mDisplayWidth, mDisplayHeight);
-
- const uint32_t fboWidth = floorf(mDisplayWidth * kFboScale);
- const uint32_t fboHeight = floorf(mDisplayHeight * kFboScale);
- mPingFbo.allocateBuffers(fboWidth, fboHeight);
- mPongFbo.allocateBuffers(fboWidth, fboHeight);
-
- if (mPingFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
- ALOGE("Invalid ping buffer");
- return mPingFbo.getStatus();
- }
- if (mPongFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
- ALOGE("Invalid pong buffer");
- return mPongFbo.getStatus();
- }
- if (mCompositionFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
- ALOGE("Invalid composition buffer");
- return mCompositionFbo.getStatus();
- }
- if (!mBlurProgram.isValid()) {
- ALOGE("Invalid shader");
- return GL_INVALID_OPERATION;
- }
- }
-
- mCompositionFbo.bind();
- glViewport(0, 0, mCompositionFbo.getBufferWidth(), mCompositionFbo.getBufferHeight());
- return NO_ERROR;
-}
-
-void BlurFilter::drawMesh(GLuint uv, GLuint position) {
-
- glEnableVertexAttribArray(uv);
- glEnableVertexAttribArray(position);
- mMeshBuffer.bind();
- glVertexAttribPointer(position, 2 /* size */, GL_FLOAT, GL_FALSE,
- 2 * sizeof(GLfloat) /* stride */, 0 /* offset */);
- glVertexAttribPointer(uv, 2 /* size */, GL_FLOAT, GL_FALSE, 0 /* stride */,
- (GLvoid*)(6 * sizeof(GLfloat)) /* offset */);
- mMeshBuffer.unbind();
-
- // draw mesh
- glDrawArrays(GL_TRIANGLES, 0 /* first */, 3 /* count */);
-}
-
-status_t BlurFilter::prepare() {
- ATRACE_NAME("BlurFilter::prepare");
-
- // Kawase is an approximation of Gaussian, but it behaves differently from it.
- // A radius transformation is required for approximating them, and also to introduce
- // non-integer steps, necessary to smoothly interpolate large radii.
- const auto radius = mRadius / 6.0f;
-
- // Calculate how many passes we'll do, based on the radius.
- // Too many passes will make the operation expensive.
- const auto passes = min(kMaxPasses, (uint32_t)ceil(radius));
-
- const float radiusByPasses = radius / (float)passes;
- const float stepX = radiusByPasses / (float)mCompositionFbo.getBufferWidth();
- const float stepY = radiusByPasses / (float)mCompositionFbo.getBufferHeight();
-
- // Let's start by downsampling and blurring the composited frame simultaneously.
- mBlurProgram.useProgram();
- glActiveTexture(GL_TEXTURE0);
- glUniform1i(mBTextureLoc, 0);
- glBindTexture(GL_TEXTURE_2D, mCompositionFbo.getTextureName());
- glUniform2f(mBOffsetLoc, stepX, stepY);
- glViewport(0, 0, mPingFbo.getBufferWidth(), mPingFbo.getBufferHeight());
- mPingFbo.bind();
- drawMesh(mBUvLoc, mBPosLoc);
-
- // And now we'll ping pong between our textures, to accumulate the result of various offsets.
- GLFramebuffer* read = &mPingFbo;
- GLFramebuffer* draw = &mPongFbo;
- glViewport(0, 0, draw->getBufferWidth(), draw->getBufferHeight());
- for (auto i = 1; i < passes; i++) {
- ATRACE_NAME("BlurFilter::renderPass");
- draw->bind();
-
- glBindTexture(GL_TEXTURE_2D, read->getTextureName());
- glUniform2f(mBOffsetLoc, stepX * i, stepY * i);
-
- drawMesh(mBUvLoc, mBPosLoc);
-
- // Swap buffers for next iteration
- auto tmp = draw;
- draw = read;
- read = tmp;
- }
- mLastDrawTarget = read;
-
- return NO_ERROR;
-}
-
-status_t BlurFilter::render(bool multiPass) {
- ATRACE_NAME("BlurFilter::render");
-
- // Now let's scale our blur up. It will be interpolated with the larger composited
- // texture for the first frames, to hide downscaling artifacts.
- GLfloat mix = fmin(1.0, mRadius / kMaxCrossFadeRadius);
-
- // When doing multiple passes, we cannot try to read mCompositionFbo, given that we'll
- // be writing onto it. Let's disable the crossfade, otherwise we'd need 1 extra frame buffer,
- // as large as the screen size.
- if (mix >= 1 || multiPass) {
- mLastDrawTarget->bindAsReadBuffer();
- glBlitFramebuffer(0, 0, mLastDrawTarget->getBufferWidth(),
- mLastDrawTarget->getBufferHeight(), mDisplayX, mDisplayY, mDisplayWidth,
- mDisplayHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
- return NO_ERROR;
- }
-
- mMixProgram.useProgram();
- glUniform1f(mMMixLoc, mix);
- glActiveTexture(GL_TEXTURE0);
- glBindTexture(GL_TEXTURE_2D, mLastDrawTarget->getTextureName());
- glUniform1i(mMTextureLoc, 0);
- glActiveTexture(GL_TEXTURE1);
- glBindTexture(GL_TEXTURE_2D, mCompositionFbo.getTextureName());
- glUniform1i(mMCompositionTextureLoc, 1);
-
- drawMesh(mMUvLoc, mMPosLoc);
-
- glUseProgram(0);
- glActiveTexture(GL_TEXTURE0);
- mEngine.checkErrors("Drawing blur mesh");
- return NO_ERROR;
-}
-
-string BlurFilter::getVertexShader() const {
- return R"SHADER(#version 300 es
- precision mediump float;
-
- in vec2 aPosition;
- in highp vec2 aUV;
- out highp vec2 vUV;
-
- void main() {
- vUV = aUV;
- gl_Position = vec4(aPosition, 0.0, 1.0);
- }
- )SHADER";
-}
-
-string BlurFilter::getFragmentShader() const {
- return R"SHADER(#version 300 es
- precision mediump float;
-
- uniform sampler2D uTexture;
- uniform vec2 uOffset;
-
- in highp vec2 vUV;
- out vec4 fragColor;
-
- void main() {
- fragColor = texture(uTexture, vUV, 0.0);
- fragColor += texture(uTexture, vUV + vec2( uOffset.x, uOffset.y), 0.0);
- fragColor += texture(uTexture, vUV + vec2( uOffset.x, -uOffset.y), 0.0);
- fragColor += texture(uTexture, vUV + vec2(-uOffset.x, uOffset.y), 0.0);
- fragColor += texture(uTexture, vUV + vec2(-uOffset.x, -uOffset.y), 0.0);
-
- fragColor = vec4(fragColor.rgb * 0.2, 1.0);
- }
- )SHADER";
-}
-
-string BlurFilter::getMixFragShader() const {
- string shader = R"SHADER(#version 300 es
- precision mediump float;
-
- in highp vec2 vUV;
- out vec4 fragColor;
-
- uniform sampler2D uCompositionTexture;
- uniform sampler2D uTexture;
- uniform float uMix;
-
- void main() {
- vec4 blurred = texture(uTexture, vUV);
- vec4 composition = texture(uCompositionTexture, vUV);
- fragColor = mix(composition, blurred, uMix);
- }
- )SHADER";
- return shader;
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/filters/BlurFilter.h b/libs/renderengine/gl/filters/BlurFilter.h
deleted file mode 100644
index 593a8fd54e..0000000000
--- a/libs/renderengine/gl/filters/BlurFilter.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright 2019 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.
- */
-
-#pragma once
-
-#include <ui/GraphicTypes.h>
-#include "../GLESRenderEngine.h"
-#include "../GLFramebuffer.h"
-#include "../GLVertexBuffer.h"
-#include "GenericProgram.h"
-
-using namespace std;
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-/**
- * This is an implementation of a Kawase blur, as described in here:
- * https://community.arm.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/
- * 00-00-00-20-66/siggraph2015_2D00_mmg_2D00_marius_2D00_notes.pdf
- */
-class BlurFilter {
-public:
- // Downsample FBO to improve performance
- static constexpr float kFboScale = 0.25f;
- // Maximum number of render passes
- static constexpr uint32_t kMaxPasses = 4;
- // To avoid downscaling artifacts, we interpolate the blurred fbo with the full composited
- // image, up to this radius.
- static constexpr float kMaxCrossFadeRadius = 30.0f;
-
- explicit BlurFilter(GLESRenderEngine& engine);
- virtual ~BlurFilter(){};
-
- // Set up render targets, redirecting output to offscreen texture.
- status_t setAsDrawTarget(const DisplaySettings&, uint32_t radius);
- // Execute blur passes, rendering to offscreen texture.
- status_t prepare();
- // Render blur to the bound framebuffer (screen).
- status_t render(bool multiPass);
-
-private:
- uint32_t mRadius;
- void drawMesh(GLuint uv, GLuint position);
- string getVertexShader() const;
- string getFragmentShader() const;
- string getMixFragShader() const;
-
- GLESRenderEngine& mEngine;
- // Frame buffer holding the composited background.
- GLFramebuffer mCompositionFbo;
- // Frame buffers holding the blur passes.
- GLFramebuffer mPingFbo;
- GLFramebuffer mPongFbo;
- uint32_t mDisplayWidth = 0;
- uint32_t mDisplayHeight = 0;
- uint32_t mDisplayX = 0;
- uint32_t mDisplayY = 0;
- // Buffer holding the final blur pass.
- GLFramebuffer* mLastDrawTarget;
-
- // VBO containing vertex and uv data of a fullscreen triangle.
- GLVertexBuffer mMeshBuffer;
-
- GenericProgram mMixProgram;
- GLuint mMPosLoc;
- GLuint mMUvLoc;
- GLuint mMMixLoc;
- GLuint mMTextureLoc;
- GLuint mMCompositionTextureLoc;
-
- GenericProgram mBlurProgram;
- GLuint mBPosLoc;
- GLuint mBUvLoc;
- GLuint mBTextureLoc;
- GLuint mBOffsetLoc;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/filters/GenericProgram.cpp b/libs/renderengine/gl/filters/GenericProgram.cpp
deleted file mode 100644
index bb35889665..0000000000
--- a/libs/renderengine/gl/filters/GenericProgram.cpp
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright 2019 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.
- */
-
-#include "GenericProgram.h"
-
-#include <GLES/gl.h>
-#include <GLES/glext.h>
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-GenericProgram::GenericProgram(GLESRenderEngine& engine) : mEngine(engine) {}
-
-GenericProgram::~GenericProgram() {
- if (mVertexShaderHandle != 0) {
- if (mProgramHandle != 0) {
- glDetachShader(mProgramHandle, mVertexShaderHandle);
- }
- glDeleteShader(mVertexShaderHandle);
- }
-
- if (mFragmentShaderHandle != 0) {
- if (mProgramHandle != 0) {
- glDetachShader(mProgramHandle, mFragmentShaderHandle);
- }
- glDeleteShader(mFragmentShaderHandle);
- }
-
- if (mProgramHandle != 0) {
- glDeleteProgram(mProgramHandle);
- }
-}
-
-void GenericProgram::compile(string vertexShader, string fragmentShader) {
- mVertexShaderHandle = compileShader(GL_VERTEX_SHADER, vertexShader);
- mFragmentShaderHandle = compileShader(GL_FRAGMENT_SHADER, fragmentShader);
- if (mVertexShaderHandle == 0 || mFragmentShaderHandle == 0) {
- ALOGE("Aborting program creation.");
- return;
- }
- mProgramHandle = createAndLink(mVertexShaderHandle, mFragmentShaderHandle);
- mEngine.checkErrors("Linking program");
-}
-
-void GenericProgram::useProgram() const {
- glUseProgram(mProgramHandle);
-}
-
-GLuint GenericProgram::compileShader(GLuint type, string src) const {
- const GLuint shader = glCreateShader(type);
- if (shader == 0) {
- mEngine.checkErrors("Creating shader");
- return 0;
- }
- const GLchar* charSrc = (const GLchar*)src.c_str();
- glShaderSource(shader, 1, &charSrc, nullptr);
- glCompileShader(shader);
-
- GLint isCompiled = 0;
- glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
- if (isCompiled == GL_FALSE) {
- GLint maxLength = 0;
- glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
- string errorLog;
- errorLog.reserve(maxLength);
- glGetShaderInfoLog(shader, maxLength, &maxLength, errorLog.data());
- glDeleteShader(shader);
- ALOGE("Error compiling shader: %s", errorLog.c_str());
- return 0;
- }
- return shader;
-}
-GLuint GenericProgram::createAndLink(GLuint vertexShader, GLuint fragmentShader) const {
- const GLuint program = glCreateProgram();
- mEngine.checkErrors("Creating program");
-
- glAttachShader(program, vertexShader);
- glAttachShader(program, fragmentShader);
- glLinkProgram(program);
- mEngine.checkErrors("Linking program");
- return program;
-}
-
-GLuint GenericProgram::getUniformLocation(const string name) const {
- if (mProgramHandle == 0) {
- ALOGE("Can't get location of %s on an invalid program.", name.c_str());
- return -1;
- }
- return glGetUniformLocation(mProgramHandle, (const GLchar*)name.c_str());
-}
-
-GLuint GenericProgram::getAttributeLocation(const string name) const {
- if (mProgramHandle == 0) {
- ALOGE("Can't get location of %s on an invalid program.", name.c_str());
- return -1;
- }
- return glGetAttribLocation(mProgramHandle, (const GLchar*)name.c_str());
-}
-
-bool GenericProgram::isValid() const {
- return mProgramHandle != 0;
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/filters/GenericProgram.h b/libs/renderengine/gl/filters/GenericProgram.h
deleted file mode 100644
index 6da2a5af58..0000000000
--- a/libs/renderengine/gl/filters/GenericProgram.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2019 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.
- */
-
-#pragma once
-
-#include <ui/GraphicTypes.h>
-#include "../GLESRenderEngine.h"
-#include "../GLFramebuffer.h"
-
-using namespace std;
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-class GenericProgram {
-public:
- explicit GenericProgram(GLESRenderEngine& renderEngine);
- ~GenericProgram();
- void compile(string vertexShader, string fragmentShader);
- bool isValid() const;
- void useProgram() const;
- GLuint getAttributeLocation(const string name) const;
- GLuint getUniformLocation(const string name) const;
-
-private:
- GLuint compileShader(GLuint type, const string src) const;
- GLuint createAndLink(GLuint vertexShader, GLuint fragmentShader) const;
-
- GLESRenderEngine& mEngine;
- GLuint mVertexShaderHandle = 0;
- GLuint mFragmentShaderHandle = 0;
- GLuint mProgramHandle = 0;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/include/renderengine/Framebuffer.h b/libs/renderengine/include/renderengine/Framebuffer.h
deleted file mode 100644
index 65111278e0..0000000000
--- a/libs/renderengine/include/renderengine/Framebuffer.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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.
- */
-
-#pragma once
-
-#include <cstdint>
-
-struct ANativeWindowBuffer;
-
-namespace android {
-namespace renderengine {
-
-class Framebuffer {
-public:
- virtual ~Framebuffer() = default;
-
- virtual bool setNativeWindowBuffer(ANativeWindowBuffer* nativeBuffer, bool isProtected,
- const bool useFramebufferCache) = 0;
-};
-
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h
index b3a617c04b..8ac0af47c6 100644
--- a/libs/renderengine/include/renderengine/LayerSettings.h
+++ b/libs/renderengine/include/renderengine/LayerSettings.h
@@ -28,6 +28,7 @@
#include <ui/GraphicTypes.h>
#include <ui/Rect.h>
#include <ui/Region.h>
+#include <ui/ShadowSettings.h>
#include <ui/StretchEffect.h>
#include <ui/Transform.h>
@@ -46,10 +47,6 @@ struct Buffer {
// Fence that will fire when the buffer is ready to be bound.
sp<Fence> fence = nullptr;
- // Texture identifier to bind the external texture to.
- // TODO(alecmouri): This is GL-specific...make the type backend-agnostic.
- uint32_t textureName = 0;
-
// Whether to use filtering when rendering the texture.
bool useTextureFiltering = false;
@@ -64,9 +61,6 @@ struct Buffer {
// overrides the alpha channel of the buffer.
bool isOpaque = false;
- // HDR color-space setting for Y410.
- bool isY410BT2020 = false;
-
float maxLuminanceNits = 0.0;
};
@@ -104,36 +98,6 @@ struct PixelSource {
half3 solidColor = half3(0.0f, 0.0f, 0.0f);
};
-/*
- * Contains the configuration for the shadows drawn by single layer. Shadow follows
- * material design guidelines.
- */
-struct ShadowSettings {
- // Boundaries of the shadow.
- FloatRect boundaries = FloatRect();
-
- // Color to the ambient shadow. The alpha is premultiplied.
- vec4 ambientColor = vec4();
-
- // Color to the spot shadow. The alpha is premultiplied. The position of the spot shadow
- // depends on the light position.
- vec4 spotColor = vec4();
-
- // Position of the light source used to cast the spot shadow.
- vec3 lightPos = vec3();
-
- // Radius of the spot light source. Smaller radius will have sharper edges,
- // larger radius will have softer shadows
- float lightRadius = 0.f;
-
- // Length of the cast shadow. If length is <= 0.f no shadows will be drawn.
- float length = 0.f;
-
- // If true fill in the casting layer is translucent and the shadow needs to fill the bounds.
- // Otherwise the shadow will only be drawn around the edges of the casting layer.
- bool casterIsTranslucent = false;
-};
-
// The settings that RenderEngine requires for correctly rendering a Layer.
struct LayerSettings {
// Geometry information
@@ -185,12 +149,10 @@ struct LayerSettings {
// compositionengine/impl/ClientCompositionRequestCache.cpp
static inline bool operator==(const Buffer& lhs, const Buffer& rhs) {
return lhs.buffer == rhs.buffer && lhs.fence == rhs.fence &&
- lhs.textureName == rhs.textureName &&
lhs.useTextureFiltering == rhs.useTextureFiltering &&
lhs.textureTransform == rhs.textureTransform &&
lhs.usePremultipliedAlpha == rhs.usePremultipliedAlpha &&
- lhs.isOpaque == rhs.isOpaque && lhs.isY410BT2020 == rhs.isY410BT2020 &&
- lhs.maxLuminanceNits == rhs.maxLuminanceNits;
+ lhs.isOpaque == rhs.isOpaque && lhs.maxLuminanceNits == rhs.maxLuminanceNits;
}
static inline bool operator==(const Geometry& lhs, const Geometry& rhs) {
@@ -203,17 +165,6 @@ static inline bool operator==(const PixelSource& lhs, const PixelSource& rhs) {
return lhs.buffer == rhs.buffer && lhs.solidColor == rhs.solidColor;
}
-static inline bool operator==(const ShadowSettings& lhs, const ShadowSettings& rhs) {
- return lhs.boundaries == rhs.boundaries && lhs.ambientColor == rhs.ambientColor &&
- lhs.spotColor == rhs.spotColor && lhs.lightPos == rhs.lightPos &&
- lhs.lightRadius == rhs.lightRadius && lhs.length == rhs.length &&
- lhs.casterIsTranslucent == rhs.casterIsTranslucent;
-}
-
-static inline bool operator!=(const ShadowSettings& lhs, const ShadowSettings& rhs) {
- return !(operator==(lhs, rhs));
-}
-
static inline bool operator==(const LayerSettings& lhs, const LayerSettings& rhs) {
if (lhs.blurRegions.size() != rhs.blurRegions.size()) {
return false;
@@ -241,13 +192,11 @@ static inline void PrintTo(const Buffer& settings, ::std::ostream* os) {
<< (settings.buffer.get() ? decodePixelFormat(settings.buffer->getPixelFormat()).c_str()
: "");
*os << "\n .fence = " << settings.fence.get();
- *os << "\n .textureName = " << settings.textureName;
*os << "\n .useTextureFiltering = " << settings.useTextureFiltering;
*os << "\n .textureTransform = ";
PrintMatrix(settings.textureTransform, os);
*os << "\n .usePremultipliedAlpha = " << settings.usePremultipliedAlpha;
*os << "\n .isOpaque = " << settings.isOpaque;
- *os << "\n .isY410BT2020 = " << settings.isY410BT2020;
*os << "\n .maxLuminanceNits = " << settings.maxLuminanceNits;
*os << "\n}";
}
diff --git a/libs/renderengine/include/renderengine/Mesh.h b/libs/renderengine/include/renderengine/Mesh.h
deleted file mode 100644
index 167f13f1bc..0000000000
--- a/libs/renderengine/include/renderengine/Mesh.h
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright 2013 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.
- */
-
-#ifndef SF_RENDER_ENGINE_MESH_H
-#define SF_RENDER_ENGINE_MESH_H
-
-#include <vector>
-
-#include <stdint.h>
-
-namespace android {
-namespace renderengine {
-
-class Mesh {
-public:
- class Builder;
-
- enum Primitive {
- TRIANGLES = 0x0004, // GL_TRIANGLES
- TRIANGLE_STRIP = 0x0005, // GL_TRIANGLE_STRIP
- TRIANGLE_FAN = 0x0006 // GL_TRIANGLE_FAN
- };
-
- ~Mesh() = default;
-
- /*
- * VertexArray handles the stride automatically.
- */
- template <typename TYPE>
- class VertexArray {
- friend class Mesh;
- float* mData;
- size_t mStride;
- size_t mOffset = 0;
- VertexArray(float* data, size_t stride) : mData(data), mStride(stride) {}
-
- public:
- // Returns a vertex array at an offset so its easier to append attributes from
- // multiple sources.
- VertexArray(VertexArray<TYPE>& other, size_t offset)
- : mData(other.mData), mStride(other.mStride), mOffset(offset) {}
-
- TYPE& operator[](size_t index) {
- return *reinterpret_cast<TYPE*>(&mData[(index + mOffset) * mStride]);
- }
- TYPE const& operator[](size_t index) const {
- return *reinterpret_cast<TYPE const*>(&mData[(index + mOffset) * mStride]);
- }
- };
-
- template <typename TYPE>
- VertexArray<TYPE> getPositionArray() {
- return VertexArray<TYPE>(getPositions(), mStride);
- }
-
- template <typename TYPE>
- VertexArray<TYPE> getTexCoordArray() {
- return VertexArray<TYPE>(getTexCoords(), mStride);
- }
-
- template <typename TYPE>
- VertexArray<TYPE> getCropCoordArray() {
- return VertexArray<TYPE>(getCropCoords(), mStride);
- }
-
- template <typename TYPE>
- VertexArray<TYPE> getShadowColorArray() {
- return VertexArray<TYPE>(getShadowColor(), mStride);
- }
-
- template <typename TYPE>
- VertexArray<TYPE> getShadowParamsArray() {
- return VertexArray<TYPE>(getShadowParams(), mStride);
- }
-
- uint16_t* getIndicesArray() { return getIndices(); }
-
- Primitive getPrimitive() const;
-
- // returns a pointer to the vertices positions
- float const* getPositions() const;
-
- // returns a pointer to the vertices texture coordinates
- float const* getTexCoords() const;
-
- // returns a pointer to the vertices crop coordinates
- float const* getCropCoords() const;
-
- // returns a pointer to colors
- float const* getShadowColor() const;
-
- // returns a pointer to the shadow params
- float const* getShadowParams() const;
-
- // returns a pointer to indices
- uint16_t const* getIndices() const;
-
- // number of vertices in this mesh
- size_t getVertexCount() const;
-
- // dimension of vertices
- size_t getVertexSize() const;
-
- // dimension of texture coordinates
- size_t getTexCoordsSize() const;
-
- size_t getShadowParamsSize() const;
-
- size_t getShadowColorSize() const;
-
- size_t getIndexCount() const;
-
- // return stride in bytes
- size_t getByteStride() const;
-
- // return stride in floats
- size_t getStride() const;
-
-private:
- Mesh(Primitive primitive, size_t vertexCount, size_t vertexSize, size_t texCoordSize,
- size_t cropCoordsSize, size_t shadowColorSize, size_t shadowParamsSize, size_t indexCount);
- Mesh(const Mesh&);
- Mesh& operator=(const Mesh&);
- Mesh const& operator=(const Mesh&) const;
-
- float* getPositions();
- float* getTexCoords();
- float* getCropCoords();
- float* getShadowColor();
- float* getShadowParams();
- uint16_t* getIndices();
-
- std::vector<float> mVertices;
- size_t mVertexCount;
- size_t mVertexSize;
- size_t mTexCoordsSize;
- size_t mCropCoordsSize;
- size_t mShadowColorSize;
- size_t mShadowParamsSize;
- size_t mStride;
- Primitive mPrimitive;
- std::vector<uint16_t> mIndices;
- size_t mIndexCount;
-};
-
-class Mesh::Builder {
-public:
- Builder& setPrimitive(Primitive primitive) {
- mPrimitive = primitive;
- return *this;
- };
- Builder& setVertices(size_t vertexCount, size_t vertexSize) {
- mVertexCount = vertexCount;
- mVertexSize = vertexSize;
- return *this;
- };
- Builder& setTexCoords(size_t texCoordsSize) {
- mTexCoordsSize = texCoordsSize;
- return *this;
- };
- Builder& setCropCoords(size_t cropCoordsSize) {
- mCropCoordsSize = cropCoordsSize;
- return *this;
- };
- Builder& setShadowAttrs() {
- mShadowParamsSize = 3;
- mShadowColorSize = 4;
- return *this;
- };
- Builder& setIndices(size_t indexCount) {
- mIndexCount = indexCount;
- return *this;
- };
- Mesh build() const {
- return Mesh{mPrimitive, mVertexCount, mVertexSize, mTexCoordsSize,
- mCropCoordsSize, mShadowColorSize, mShadowParamsSize, mIndexCount};
- }
-
-private:
- size_t mVertexCount = 0;
- size_t mVertexSize = 0;
- size_t mTexCoordsSize = 0;
- size_t mCropCoordsSize = 0;
- size_t mShadowColorSize = 0;
- size_t mShadowParamsSize = 0;
- size_t mIndexCount = 0;
- Primitive mPrimitive;
-};
-
-} // namespace renderengine
-} // namespace android
-#endif /* SF_RENDER_ENGINE_MESH_H */
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index 0d910c9b29..818d0350c0 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -22,8 +22,6 @@
#include <math/mat4.h>
#include <renderengine/DisplaySettings.h>
#include <renderengine/ExternalTexture.h>
-#include <renderengine/Framebuffer.h>
-#include <renderengine/Image.h>
#include <renderengine/LayerSettings.h>
#include <stdint.h>
#include <sys/types.h>
@@ -95,8 +93,6 @@ public:
};
enum class RenderEngineType {
- GLES = 1,
- THREADED = 2,
SKIA_GL = 3,
SKIA_GL_THREADED = 4,
SKIA_VK = 5,
@@ -111,14 +107,11 @@ public:
// This interface, while still in use until a suitable replacement is built,
// should be considered deprecated, minus some methods which still may be
// used to support legacy behavior.
- virtual std::future<void> primeCache() = 0;
+ virtual std::future<void> primeCache(bool shouldPrimeUltraHDR) = 0;
// dump the extension strings. always call the base class.
virtual void dump(std::string& result) = 0;
- virtual void genTextures(size_t count, uint32_t* names) = 0;
- virtual void deleteTextures(size_t count, uint32_t const* names) = 0;
-
// queries that are required to be thread safe
virtual size_t getMaxTextureSize() const = 0;
virtual size_t getMaxViewportDims() const = 0;
@@ -152,9 +145,6 @@ public:
// @param layers The layers to draw onto the display, in Z-order.
// @param buffer The buffer which will be drawn to. This buffer will be
// ready once drawFence fires.
- // @param useFramebufferCache True if the framebuffer cache should be used.
- // If an implementation does not cache output framebuffers, then this
- // parameter does nothing.
// @param bufferFence Fence signalling that the buffer is ready to be drawn
// to.
// @return A future object of FenceResult indicating whether drawing was
@@ -162,7 +152,6 @@ public:
virtual ftl::Future<FenceResult> drawLayers(const DisplaySettings& display,
const std::vector<LayerSettings>& layers,
const std::shared_ptr<ExternalTexture>& buffer,
- const bool useFramebufferCache,
base::unique_fd&& bufferFence);
// Clean-up method that should be called on the main thread after the
@@ -171,8 +160,6 @@ public:
// being drawn, then the implementation is free to silently ignore this call.
virtual void cleanupPostRender() = 0;
- virtual void cleanFramebufferCache() = 0;
-
// Returns the priority this context was actually created with. Note: this
// may not be the same as specified at context creation time, due to
// implementation limits on the number of contexts that can be created at a
@@ -204,7 +191,7 @@ public:
virtual void setEnableTracing(bool /*tracingEnabled*/) {}
protected:
- RenderEngine() : RenderEngine(RenderEngineType::GLES) {}
+ RenderEngine() : RenderEngine(RenderEngineType::SKIA_GL) {}
RenderEngine(RenderEngineType type) : mRenderEngineType(type) {}
@@ -253,8 +240,7 @@ protected:
virtual void drawLayersInternal(
const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
const DisplaySettings& display, const std::vector<LayerSettings>& layers,
- const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
- base::unique_fd&& bufferFence) = 0;
+ const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) = 0;
};
struct RenderEngineCreationArgs {
@@ -271,14 +257,13 @@ struct RenderEngineCreationArgs {
private:
// must be created by Builder via constructor with full argument list
- RenderEngineCreationArgs(int _pixelFormat, uint32_t _imageCacheSize, bool _useColorManagement,
+ RenderEngineCreationArgs(int _pixelFormat, uint32_t _imageCacheSize,
bool _enableProtectedContext, bool _precacheToneMapperShaderOnly,
bool _supportsBackgroundBlur,
RenderEngine::ContextPriority _contextPriority,
RenderEngine::RenderEngineType _renderEngineType)
: pixelFormat(_pixelFormat),
imageCacheSize(_imageCacheSize),
- useColorManagement(_useColorManagement),
enableProtectedContext(_enableProtectedContext),
precacheToneMapperShaderOnly(_precacheToneMapperShaderOnly),
supportsBackgroundBlur(_supportsBackgroundBlur),
@@ -298,10 +283,6 @@ struct RenderEngineCreationArgs::Builder {
this->imageCacheSize = imageCacheSize;
return *this;
}
- Builder& setUseColorManagerment(bool useColorManagement) {
- this->useColorManagement = useColorManagement;
- return *this;
- }
Builder& setEnableProtectedContext(bool enableProtectedContext) {
this->enableProtectedContext = enableProtectedContext;
return *this;
@@ -323,16 +304,15 @@ struct RenderEngineCreationArgs::Builder {
return *this;
}
RenderEngineCreationArgs build() const {
- return RenderEngineCreationArgs(pixelFormat, imageCacheSize, useColorManagement,
- enableProtectedContext, precacheToneMapperShaderOnly,
- supportsBackgroundBlur, contextPriority, renderEngineType);
+ return RenderEngineCreationArgs(pixelFormat, imageCacheSize, enableProtectedContext,
+ precacheToneMapperShaderOnly, supportsBackgroundBlur,
+ contextPriority, renderEngineType);
}
private:
// 1 means RGBA_8888
int pixelFormat = 1;
uint32_t imageCacheSize = 0;
- bool useColorManagement = true;
bool enableProtectedContext = false;
bool precacheToneMapperShaderOnly = false;
bool supportsBackgroundBlur = false;
diff --git a/libs/renderengine/include/renderengine/Texture.h b/libs/renderengine/include/renderengine/Texture.h
deleted file mode 100644
index c69ace0603..0000000000
--- a/libs/renderengine/include/renderengine/Texture.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2013 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.
- */
-
-#ifndef SF_RENDER_ENGINE_TEXTURE_H
-#define SF_RENDER_ENGINE_TEXTURE_H
-
-#include <stdint.h>
-
-#include <math/mat4.h>
-
-namespace android {
-namespace renderengine {
-
-class Texture {
-public:
- enum Target { TEXTURE_2D = 0x0DE1, TEXTURE_EXTERNAL = 0x8D65 };
-
- Texture();
- Texture(Target textureTarget, uint32_t textureName);
- ~Texture();
-
- void init(Target textureTarget, uint32_t textureName);
-
- void setMatrix(float const* matrix);
- void setFiltering(bool enabled);
- void setDimensions(size_t width, size_t height);
-
- uint32_t getTextureName() const;
- uint32_t getTextureTarget() const;
-
- const mat4& getMatrix() const;
- bool getFiltering() const;
- size_t getWidth() const;
- size_t getHeight() const;
-
-private:
- uint32_t mTextureName;
- uint32_t mTextureTarget;
- size_t mWidth;
- size_t mHeight;
- bool mFiltering;
- mat4 mTextureMatrix;
-};
-
-} // namespace renderengine
-} // namespace android
-#endif /* SF_RENDER_ENGINE_TEXTURE_H */
diff --git a/libs/renderengine/include/renderengine/mock/Framebuffer.h b/libs/renderengine/include/renderengine/mock/Framebuffer.h
deleted file mode 100644
index dfb6a4e41e..0000000000
--- a/libs/renderengine/include/renderengine/mock/Framebuffer.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.
- */
-
-#pragma once
-
-#include <gmock/gmock.h>
-#include <renderengine/Framebuffer.h>
-
-namespace android {
-namespace renderengine {
-namespace mock {
-
-class Framebuffer : public renderengine::Framebuffer {
-public:
- Framebuffer();
- ~Framebuffer() override;
-
- MOCK_METHOD3(setNativeWindowBuffer, bool(ANativeWindowBuffer*, bool, const bool));
-};
-
-} // namespace mock
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/include/renderengine/mock/Image.h b/libs/renderengine/include/renderengine/mock/Image.h
deleted file mode 100644
index 2b0eed1173..0000000000
--- a/libs/renderengine/include/renderengine/mock/Image.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.
- */
-
-#pragma once
-
-#include <gmock/gmock.h>
-#include <renderengine/Image.h>
-
-namespace android {
-namespace renderengine {
-namespace mock {
-
-class Image : public renderengine::Image {
-public:
- Image();
- ~Image() override;
-
- MOCK_METHOD2(setNativeWindowBuffer, bool(ANativeWindowBuffer* buffer, bool isProtected));
-};
-
-} // namespace mock
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/include/renderengine/mock/RenderEngine.h b/libs/renderengine/include/renderengine/mock/RenderEngine.h
index d3035e24a5..a58a65ca9f 100644
--- a/libs/renderengine/include/renderengine/mock/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/mock/RenderEngine.h
@@ -19,9 +19,7 @@
#include <gmock/gmock.h>
#include <renderengine/DisplaySettings.h>
#include <renderengine/LayerSettings.h>
-#include <renderengine/Mesh.h>
#include <renderengine/RenderEngine.h>
-#include <renderengine/Texture.h>
#include <ui/Fence.h>
#include <ui/GraphicBuffer.h>
#include <ui/Region.h>
@@ -35,11 +33,8 @@ public:
RenderEngine();
~RenderEngine() override;
- MOCK_METHOD0(primeCache, std::future<void>());
+ MOCK_METHOD1(primeCache, std::future<void>(bool));
MOCK_METHOD1(dump, void(std::string&));
- MOCK_METHOD2(genTextures, void(size_t, uint32_t*));
- MOCK_METHOD2(deleteTextures, void(size_t, uint32_t const*));
- MOCK_METHOD1(drawMesh, void(const renderengine::Mesh&));
MOCK_CONST_METHOD0(getMaxTextureSize, size_t());
MOCK_CONST_METHOD0(getMaxViewportDims, size_t());
MOCK_CONST_METHOD0(isProtected, bool());
@@ -47,15 +42,14 @@ public:
MOCK_METHOD1(useProtectedContext, void(bool));
MOCK_METHOD0(cleanupPostRender, void());
MOCK_CONST_METHOD0(canSkipPostRenderCleanup, bool());
- MOCK_METHOD5(drawLayers,
+ MOCK_METHOD4(drawLayers,
ftl::Future<FenceResult>(const DisplaySettings&, const std::vector<LayerSettings>&,
- const std::shared_ptr<ExternalTexture>&, const bool,
+ const std::shared_ptr<ExternalTexture>&,
base::unique_fd&&));
- MOCK_METHOD6(drawLayersInternal,
+ MOCK_METHOD5(drawLayersInternal,
void(const std::shared_ptr<std::promise<FenceResult>>&&, const DisplaySettings&,
const std::vector<LayerSettings>&, const std::shared_ptr<ExternalTexture>&,
- const bool, base::unique_fd&&));
- MOCK_METHOD0(cleanFramebufferCache, void());
+ base::unique_fd&&));
MOCK_METHOD0(getContextPriority, int());
MOCK_METHOD0(supportsBackgroundBlur, bool());
MOCK_METHOD1(onActiveDisplaySizeChanged, void(ui::Size));
diff --git a/libs/renderengine/include/renderengine/private/Description.h b/libs/renderengine/include/renderengine/private/Description.h
deleted file mode 100644
index fa6ec10b6e..0000000000
--- a/libs/renderengine/include/renderengine/private/Description.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright 2013 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.
- */
-
-#ifndef SF_RENDER_ENGINE_DESCRIPTION_H_
-#define SF_RENDER_ENGINE_DESCRIPTION_H_
-
-#include <renderengine/Texture.h>
-#include <ui/GraphicTypes.h>
-
-namespace android {
-namespace renderengine {
-
-/*
- * This is the structure that holds the state of the rendering engine.
- * This class is used to generate a corresponding GLSL program and set the
- * appropriate uniform.
- */
-struct Description {
- enum class TransferFunction : int {
- LINEAR,
- SRGB,
- ST2084,
- HLG, // Hybrid Log-Gamma for HDR.
- };
-
- static TransferFunction dataSpaceToTransferFunction(ui::Dataspace dataSpace);
-
- Description() = default;
- ~Description() = default;
-
- bool hasInputTransformMatrix() const;
- bool hasOutputTransformMatrix() const;
- bool hasColorMatrix() const;
- bool hasDisplayColorMatrix() const;
-
- // whether textures are premultiplied
- bool isPremultipliedAlpha = false;
- // whether this layer is marked as opaque
- bool isOpaque = true;
-
- // corner radius of the layer
- float cornerRadius = 0;
-
- // Size of the rounded rectangle we are cropping to
- half2 cropSize;
-
- // Texture this layer uses
- Texture texture;
- bool textureEnabled = false;
-
- // color used when texturing is disabled or when setting alpha.
- half4 color;
-
- // true if the sampled pixel values are in Y410/BT2020 rather than RGBA
- bool isY410BT2020 = false;
-
- // transfer functions for the input/output
- TransferFunction inputTransferFunction = TransferFunction::LINEAR;
- TransferFunction outputTransferFunction = TransferFunction::LINEAR;
-
- float displayMaxLuminance;
- float maxMasteringLuminance;
- float maxContentLuminance;
-
- // projection matrix
- mat4 projectionMatrix;
-
- // The color matrix will be applied in linear space right before OETF.
- mat4 colorMatrix;
- // The display color matrix will be applied in gamma space after OETF
- mat4 displayColorMatrix;
- mat4 inputTransformMatrix;
- mat4 outputTransformMatrix;
-
- // True if this layer will draw a shadow.
- bool drawShadows = false;
-};
-
-} // namespace renderengine
-} // namespace android
-
-#endif /* SF_RENDER_ENGINE_DESCRIPTION_H_ */
diff --git a/libs/renderengine/skia/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp
index e8ad081e60..92fe4c0b47 100644
--- a/libs/renderengine/skia/AutoBackendTexture.cpp
+++ b/libs/renderengine/skia/AutoBackendTexture.cpp
@@ -20,6 +20,13 @@
#define LOG_TAG "RenderEngine"
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#include <SkImage.h>
+#include <include/gpu/ganesh/SkImageGanesh.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
+#include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
+#include <include/gpu/vk/GrVkTypes.h>
+#include <android/hardware_buffer.h>
#include "ColorSpaces.h"
#include "log/log_main.h"
#include "utils/Trace.h"
@@ -35,13 +42,44 @@ AutoBackendTexture::AutoBackendTexture(GrDirectContext* context, AHardwareBuffer
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(buffer, &desc);
bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
- GrBackendFormat backendFormat =
- GrAHardwareBufferUtils::GetBackendFormat(context, buffer, desc.format, false);
- mBackendTexture =
- GrAHardwareBufferUtils::MakeBackendTexture(context, buffer, desc.width, desc.height,
- &mDeleteProc, &mUpdateProc, &mImageCtx,
- createProtectedImage, backendFormat,
- isOutputBuffer);
+ GrBackendFormat backendFormat;
+
+ GrBackendApi backend = context->backend();
+ if (backend == GrBackendApi::kOpenGL) {
+ backendFormat =
+ GrAHardwareBufferUtils::GetGLBackendFormat(context, desc.format, false);
+ mBackendTexture =
+ GrAHardwareBufferUtils::MakeGLBackendTexture(context,
+ buffer,
+ desc.width,
+ desc.height,
+ &mDeleteProc,
+ &mUpdateProc,
+ &mImageCtx,
+ createProtectedImage,
+ backendFormat,
+ isOutputBuffer);
+ } else if (backend == GrBackendApi::kVulkan) {
+ backendFormat =
+ GrAHardwareBufferUtils::GetVulkanBackendFormat(context,
+ buffer,
+ desc.format,
+ false);
+ mBackendTexture =
+ GrAHardwareBufferUtils::MakeVulkanBackendTexture(context,
+ buffer,
+ desc.width,
+ desc.height,
+ &mDeleteProc,
+ &mUpdateProc,
+ &mImageCtx,
+ createProtectedImage,
+ backendFormat,
+ isOutputBuffer);
+ } else {
+ LOG_ALWAYS_FATAL("Unexpected backend %d", backend);
+ }
+
mColorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
if (!mBackendTexture.isValid() || !desc.width || !desc.height) {
LOG_ALWAYS_FATAL("Failed to create a valid texture. [%p]:[%d,%d] isProtected:%d "
@@ -79,7 +117,7 @@ void AutoBackendTexture::releaseSurfaceProc(SkSurface::ReleaseContext releaseCon
// releaseImageProc is invoked by SkImage, when the texture is no longer in use.
// "releaseContext" contains an "AutoBackendTexture*".
-void AutoBackendTexture::releaseImageProc(SkImage::ReleaseContext releaseContext) {
+void AutoBackendTexture::releaseImageProc(SkImages::ReleaseContext releaseContext) {
AutoBackendTexture* textureRelease = reinterpret_cast<AutoBackendTexture*>(releaseContext);
textureRelease->unref(false);
}
@@ -89,7 +127,7 @@ void logFatalTexture(const char* msg, const GrBackendTexture& tex, ui::Dataspace
switch (tex.backend()) {
case GrBackendApi::kOpenGL: {
GrGLTextureInfo textureInfo;
- bool retrievedTextureInfo = tex.getGLTextureInfo(&textureInfo);
+ bool retrievedTextureInfo = GrBackendTextures::GetGLTextureInfo(tex, &textureInfo);
LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d"
"\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i "
"texType: %i\n\t\tGrGLTextureInfo: success: %i fTarget: %u fFormat: %u"
@@ -102,7 +140,7 @@ void logFatalTexture(const char* msg, const GrBackendTexture& tex, ui::Dataspace
}
case GrBackendApi::kVulkan: {
GrVkImageInfo imageInfo;
- bool retrievedImageInfo = tex.getVkImageInfo(&imageInfo);
+ bool retrievedImageInfo = GrBackendTextures::GetVkImageInfo(tex, &imageInfo);
LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d"
"\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i "
"texType: %i\n\t\tVkImageInfo: success: %i fFormat: %i "
@@ -136,8 +174,9 @@ sk_sp<SkImage> AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaTyp
}
sk_sp<SkImage> image =
- SkImage::MakeFromTexture(context, mBackendTexture, kTopLeft_GrSurfaceOrigin, colorType,
- alphaType, toSkColorSpace(dataspace), releaseImageProc, this);
+ SkImages::BorrowTextureFrom(context, mBackendTexture, kTopLeft_GrSurfaceOrigin,
+ colorType, alphaType, toSkColorSpace(dataspace),
+ releaseImageProc, this);
if (image.get()) {
// The following ref will be counteracted by releaseProc, when SkImage is discarded.
ref();
@@ -157,10 +196,10 @@ sk_sp<SkSurface> AutoBackendTexture::getOrCreateSurface(ui::Dataspace dataspace,
LOG_ALWAYS_FATAL_IF(!mIsOutputBuffer, "You can't generate a SkSurface for a read-only texture");
if (!mSurface.get() || mDataspace != dataspace) {
sk_sp<SkSurface> surface =
- SkSurface::MakeFromBackendTexture(context, mBackendTexture,
- kTopLeft_GrSurfaceOrigin, 0, mColorType,
- toSkColorSpace(dataspace), nullptr,
- releaseSurfaceProc, this);
+ SkSurfaces::WrapBackendTexture(context, mBackendTexture,
+ kTopLeft_GrSurfaceOrigin, 0, mColorType,
+ toSkColorSpace(dataspace), nullptr,
+ releaseSurfaceProc, this);
if (surface.get()) {
// The following ref will be counteracted by releaseProc, when SkSurface is discarded.
ref();
diff --git a/libs/renderengine/skia/AutoBackendTexture.h b/libs/renderengine/skia/AutoBackendTexture.h
index 00b901be11..509ac40f77 100644
--- a/libs/renderengine/skia/AutoBackendTexture.h
+++ b/libs/renderengine/skia/AutoBackendTexture.h
@@ -144,7 +144,7 @@ private:
CleanupManager& mCleanupMgr;
static void releaseSurfaceProc(SkSurface::ReleaseContext releaseContext);
- static void releaseImageProc(SkImage::ReleaseContext releaseContext);
+ static void releaseImageProc(SkImages::ReleaseContext releaseContext);
int mUsageCount = 0;
diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp
index f6b91839a3..abe0d9b0a6 100644
--- a/libs/renderengine/skia/Cache.cpp
+++ b/libs/renderengine/skia/Cache.cpp
@@ -17,6 +17,7 @@
#include "AutoBackendTexture.h"
#include "SkiaRenderEngine.h"
#include "android-base/unique_fd.h"
+#include "cutils/properties.h"
#include "renderengine/DisplaySettings.h"
#include "renderengine/LayerSettings.h"
#include "renderengine/impl/ExternalTexture.h"
@@ -29,8 +30,6 @@
namespace android::renderengine::skia {
namespace {
-// Warming shader cache, not framebuffer cache.
-constexpr bool kUseFrameBufferCache = false;
// clang-format off
// Any non-identity matrix will do.
@@ -51,6 +50,15 @@ const auto kFlip = mat4(1.1f, -0.1f, 0.f, 0.f,
// a color correction effect is added to the shader.
constexpr auto kDestDataSpace = ui::Dataspace::SRGB;
constexpr auto kOtherDataSpace = ui::Dataspace::DISPLAY_P3;
+constexpr auto kBT2020DataSpace = ui::Dataspace::BT2020_ITU_PQ;
+constexpr auto kExtendedHdrDataSpce =
+ static_cast<ui::Dataspace>(ui::Dataspace::RANGE_EXTENDED | ui::Dataspace::TRANSFER_SRGB |
+ ui::Dataspace::STANDARD_DCI_P3);
+// Dimming is needed to trigger linear effects for some dataspace pairs
+const std::array<float, 3> kLayerWhitePoints = {
+ 1000.0f, 500.0f,
+ 100.0f, // trigger dithering by dimming below 20%
+};
} // namespace
static void drawShadowLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
@@ -65,9 +73,12 @@ static void drawShadowLayers(SkiaRenderEngine* renderengine, const DisplaySettin
.geometry =
Geometry{
.boundaries = rect,
- .roundedCornersCrop = rect,
.roundedCornersRadius = {50.f, 50.f},
+ .roundedCornersCrop = rect,
},
+ .alpha = 1,
+ // setting this is mandatory for shadows and blurs
+ .skipContentDraw = true,
// drawShadow ignores alpha
.shadow =
ShadowSettings{
@@ -78,16 +89,13 @@ static void drawShadowLayers(SkiaRenderEngine* renderengine, const DisplaySettin
.lightRadius = 2500.0f,
.length = 15.f,
},
- // setting this is mandatory for shadows and blurs
- .skipContentDraw = true,
- .alpha = 1,
};
LayerSettings caster{
.geometry =
Geometry{
.boundaries = smallerRect,
- .roundedCornersCrop = rect,
.roundedCornersRadius = {50.f, 50.f},
+ .roundedCornersCrop = rect,
},
.source =
PixelSource{
@@ -116,8 +124,7 @@ static void drawShadowLayers(SkiaRenderEngine* renderengine, const DisplaySettin
caster.geometry.positionTransform = transform;
auto layers = std::vector<LayerSettings>{layer, caster};
- renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
- base::unique_fd());
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
}
}
@@ -129,10 +136,10 @@ static void drawImageLayers(SkiaRenderEngine* renderengine, const DisplaySetting
LayerSettings layer{
.geometry =
Geometry{
+ .boundaries = rect,
// The position transform doesn't matter when the reduced shader mode
// in in effect. A matrix transform stage is always included.
.positionTransform = mat4(),
- .boundaries = rect,
.roundedCornersCrop = rect,
},
.source = PixelSource{.buffer =
@@ -154,8 +161,7 @@ static void drawImageLayers(SkiaRenderEngine* renderengine, const DisplaySetting
for (auto alpha : {half(.2f), half(1.0f)}) {
layer.alpha = alpha;
auto layers = std::vector<LayerSettings>{layer};
- renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
- base::unique_fd());
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
}
}
}
@@ -183,8 +189,7 @@ static void drawSolidLayers(SkiaRenderEngine* renderengine, const DisplaySetting
for (float roundedCornersRadius : {0.0f, 50.f}) {
layer.geometry.roundedCornersRadius = {roundedCornersRadius, roundedCornersRadius};
auto layers = std::vector<LayerSettings>{layer};
- renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
- base::unique_fd());
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
}
}
}
@@ -207,8 +212,7 @@ static void drawBlurLayers(SkiaRenderEngine* renderengine, const DisplaySettings
for (int radius : {9, 60}) {
layer.backgroundBlurRadius = radius;
auto layers = std::vector<LayerSettings>{layer};
- renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
- base::unique_fd());
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
}
}
@@ -254,8 +258,7 @@ static void drawClippedLayers(SkiaRenderEngine* renderengine, const DisplaySetti
for (float alpha : {0.5f, 1.f}) {
layer.alpha = alpha;
auto layers = std::vector<LayerSettings>{layer};
- renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
- base::unique_fd());
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
}
}
}
@@ -270,11 +273,11 @@ static void drawPIPImageLayer(SkiaRenderEngine* renderengine, const DisplaySetti
LayerSettings layer{
.geometry =
Geometry{
+ .boundaries = rect,
// Note that this flip matrix only makes a difference when clipping,
// which happens in this layer because the roundrect crop is just a bit
// larger than the layer bounds.
.positionTransform = kFlip,
- .boundaries = rect,
.roundedCornersRadius = {94.2551f, 94.2551f},
.roundedCornersCrop = FloatRect(-93.75, 0, displayRect.width() + 93.75,
displayRect.height()),
@@ -282,17 +285,17 @@ static void drawPIPImageLayer(SkiaRenderEngine* renderengine, const DisplaySetti
.source = PixelSource{.buffer =
Buffer{
.buffer = srcTexture,
- .maxLuminanceNits = 1000.f,
- .isOpaque = 0,
.usePremultipliedAlpha = 1,
+ .isOpaque = 0,
+ .maxLuminanceNits = 1000.f,
}},
- .sourceDataspace = kOtherDataSpace,
.alpha = 1,
+ .sourceDataspace = kOtherDataSpace,
};
auto layers = std::vector<LayerSettings>{layer};
- renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache, base::unique_fd());
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
}
static void drawHolePunchLayer(SkiaRenderEngine* renderengine, const DisplaySettings& display,
@@ -303,10 +306,10 @@ static void drawHolePunchLayer(SkiaRenderEngine* renderengine, const DisplaySett
LayerSettings layer{
.geometry =
Geometry{
- .positionTransform = kScaleAndTranslate,
// the boundaries have to be smaller than the rounded crop so that
// clipRRect is used instead of drawRRect
.boundaries = small,
+ .positionTransform = kScaleAndTranslate,
.roundedCornersRadius = {50.f, 50.f},
.roundedCornersCrop = rect,
},
@@ -314,14 +317,306 @@ static void drawHolePunchLayer(SkiaRenderEngine* renderengine, const DisplaySett
PixelSource{
.solidColor = half3(0.f, 0.f, 0.f),
},
- .sourceDataspace = kDestDataSpace,
.alpha = 0,
+ .sourceDataspace = kDestDataSpace,
.disableBlending = true,
};
auto layers = std::vector<LayerSettings>{layer};
- renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache, base::unique_fd());
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+}
+
+static void drawImageDimmedLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
+ const std::shared_ptr<ExternalTexture>& dstTexture,
+ const std::shared_ptr<ExternalTexture>& srcTexture) {
+ const Rect& displayRect = display.physicalDisplay;
+ FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+ LayerSettings layer{
+ .geometry =
+ Geometry{
+ // The position transform doesn't matter when the reduced shader mode
+ // in in effect. A matrix transform stage is always included.
+ .positionTransform = mat4(),
+ .boundaries = rect,
+ .roundedCornersCrop = rect,
+ .roundedCornersRadius = {0.f, 0.f},
+ },
+ .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
+ .maxLuminanceNits = 1000.f,
+ .usePremultipliedAlpha = true,
+ .isOpaque = true}},
+ .alpha = 1.f,
+ .sourceDataspace = kDestDataSpace,
+ };
+
+ std::vector<LayerSettings> layers;
+
+ for (auto layerWhitePoint : kLayerWhitePoints) {
+ layer.whitePointNits = layerWhitePoint;
+ layers.push_back(layer);
+ }
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+}
+
+static void drawTransparentImageDimmedLayers(SkiaRenderEngine* renderengine,
+ const DisplaySettings& display,
+ const std::shared_ptr<ExternalTexture>& dstTexture,
+ const std::shared_ptr<ExternalTexture>& srcTexture) {
+ const Rect& displayRect = display.physicalDisplay;
+ FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+ LayerSettings layer{
+ .geometry =
+ Geometry{
+ .positionTransform = mat4(),
+ .boundaries = rect,
+ .roundedCornersCrop = rect,
+ },
+ .source = PixelSource{.buffer =
+ Buffer{
+ .buffer = srcTexture,
+ .maxLuminanceNits = 1000.f,
+ .usePremultipliedAlpha = true,
+ .isOpaque = false,
+ }},
+ .sourceDataspace = kDestDataSpace,
+ };
+
+ for (auto roundedCornerRadius : {0.f, 50.f}) {
+ layer.geometry.roundedCornersRadius = {roundedCornerRadius, roundedCornerRadius};
+ for (auto alpha : {0.5f, 1.0f}) {
+ layer.alpha = alpha;
+ for (auto isOpaque : {true, false}) {
+ if (roundedCornerRadius == 0.f && isOpaque) {
+ // already covered in drawImageDimmedLayers
+ continue;
+ }
+
+ layer.source.buffer.isOpaque = isOpaque;
+ std::vector<LayerSettings> layers;
+
+ for (auto layerWhitePoint : kLayerWhitePoints) {
+ layer.whitePointNits = layerWhitePoint;
+ layers.push_back(layer);
+ }
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+ }
+ }
+ }
+}
+
+static void drawClippedDimmedImageLayers(SkiaRenderEngine* renderengine,
+ const DisplaySettings& display,
+ const std::shared_ptr<ExternalTexture>& dstTexture,
+ const std::shared_ptr<ExternalTexture>& srcTexture) {
+ const Rect& displayRect = display.physicalDisplay;
+
+ // If rect and boundary is too small compared to roundedCornersRadius, Skia will switch to
+ // blending instead of EllipticalRRect, so enlarge them a bit.
+ FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+ FloatRect boundary(0, 0, displayRect.width(),
+ displayRect.height() - 20); // boundary is smaller
+ LayerSettings layer{
+ .geometry =
+ Geometry{
+ .positionTransform = mat4(),
+ .boundaries = boundary,
+ .roundedCornersCrop = rect,
+ .roundedCornersRadius = {27.f, 27.f},
+ },
+ .source = PixelSource{.buffer =
+ Buffer{
+ .buffer = srcTexture,
+ .maxLuminanceNits = 1000.f,
+ .usePremultipliedAlpha = true,
+ .isOpaque = false,
+ }},
+ .alpha = 1.f,
+ .sourceDataspace = kDestDataSpace,
+ };
+
+ std::array<mat4, 2> transforms = {kScaleAndTranslate, kScaleAsymmetric};
+
+ constexpr float radius = 27.f;
+
+ for (size_t i = 0; i < transforms.size(); i++) {
+ layer.geometry.positionTransform = transforms[i];
+ layer.geometry.roundedCornersRadius = {radius, radius};
+
+ std::vector<LayerSettings> layers;
+
+ for (auto layerWhitePoint : kLayerWhitePoints) {
+ layer.whitePointNits = layerWhitePoint;
+ layers.push_back(layer);
+ }
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+ }
+}
+
+static void drawSolidDimmedLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
+ const std::shared_ptr<ExternalTexture>& dstTexture) {
+ const Rect& displayRect = display.physicalDisplay;
+ FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+ LayerSettings layer{
+ .geometry =
+ Geometry{
+ .boundaries = rect,
+ .roundedCornersCrop = rect,
+ },
+ .source =
+ PixelSource{
+ .solidColor = half3(0.1f, 0.2f, 0.3f),
+ },
+ .alpha = 1.f,
+ };
+
+ std::vector<LayerSettings> layers;
+
+ for (auto layerWhitePoint : kLayerWhitePoints) {
+ layer.whitePointNits = layerWhitePoint;
+ layers.push_back(layer);
+ }
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+}
+
+static void drawBT2020ImageLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
+ const std::shared_ptr<ExternalTexture>& dstTexture,
+ const std::shared_ptr<ExternalTexture>& srcTexture) {
+ const Rect& displayRect = display.physicalDisplay;
+ FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+ LayerSettings layer{
+ .geometry =
+ Geometry{
+ // The position transform doesn't matter when the reduced shader mode
+ // in in effect. A matrix transform stage is always included.
+ .positionTransform = mat4(),
+ .boundaries = rect,
+ .roundedCornersCrop = rect,
+ .roundedCornersRadius = {0.f, 0.f},
+ },
+ .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
+ .maxLuminanceNits = 1000.f,
+ .usePremultipliedAlpha = true,
+ .isOpaque = true}},
+ .alpha = 1.f,
+ .sourceDataspace = kBT2020DataSpace,
+ };
+
+ for (auto alpha : {0.5f, 1.f}) {
+ layer.alpha = alpha;
+ std::vector<LayerSettings> layers;
+ layer.whitePointNits = -1.f;
+ layers.push_back(layer);
+
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+ }
+}
+static void drawBT2020ClippedImageLayers(SkiaRenderEngine* renderengine,
+ const DisplaySettings& display,
+ const std::shared_ptr<ExternalTexture>& dstTexture,
+ const std::shared_ptr<ExternalTexture>& srcTexture) {
+ const Rect& displayRect = display.physicalDisplay;
+
+ // If rect and boundary is too small compared to roundedCornersRadius, Skia will switch to
+ // blending instead of EllipticalRRect, so enlarge them a bit.
+ FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+ FloatRect boundary(0, 0, displayRect.width(),
+ displayRect.height() - 10); // boundary is smaller
+ LayerSettings layer{
+ .geometry =
+ Geometry{
+ .positionTransform = kScaleAsymmetric,
+ .boundaries = boundary,
+ .roundedCornersCrop = rect,
+ .roundedCornersRadius = {64.1f, 64.1f},
+ },
+ .source = PixelSource{.buffer =
+ Buffer{
+ .buffer = srcTexture,
+ .maxLuminanceNits = 1000.f,
+ .usePremultipliedAlpha = true,
+ .isOpaque = true,
+ }},
+ .alpha = 0.5f,
+ .sourceDataspace = kBT2020DataSpace,
+ };
+
+ std::vector<LayerSettings> layers = {layer};
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+}
+
+static void drawExtendedHDRImageLayers(SkiaRenderEngine* renderengine,
+ const DisplaySettings& display,
+ const std::shared_ptr<ExternalTexture>& dstTexture,
+ const std::shared_ptr<ExternalTexture>& srcTexture) {
+ const Rect& displayRect = display.physicalDisplay;
+ FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+ LayerSettings layer{
+ .geometry =
+ Geometry{
+ // The position transform doesn't matter when the reduced shader mode
+ // in in effect. A matrix transform stage is always included.
+ .positionTransform = mat4(),
+ .boundaries = rect,
+ .roundedCornersCrop = rect,
+ .roundedCornersRadius = {50.f, 50.f},
+ },
+ .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
+ .maxLuminanceNits = 1000.f,
+ .usePremultipliedAlpha = true,
+ .isOpaque = true}},
+ .alpha = 0.5f,
+ .sourceDataspace = kExtendedHdrDataSpce,
+ };
+
+ for (auto roundedCornerRadius : {0.f, 50.f}) {
+ layer.geometry.roundedCornersRadius = {roundedCornerRadius, roundedCornerRadius};
+ for (auto alpha : {0.5f, 1.f}) {
+ layer.alpha = alpha;
+ std::vector<LayerSettings> layers;
+
+ for (auto layerWhitePoint : kLayerWhitePoints) {
+ layer.whitePointNits = layerWhitePoint;
+ layers.push_back(layer);
+ }
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+ }
+ }
+}
+
+static void drawP3ImageLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
+ const std::shared_ptr<ExternalTexture>& dstTexture,
+ const std::shared_ptr<ExternalTexture>& srcTexture) {
+ const Rect& displayRect = display.physicalDisplay;
+ FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+ LayerSettings layer{
+ .geometry =
+ Geometry{
+ // The position transform doesn't matter when the reduced shader mode
+ // in in effect. A matrix transform stage is always included.
+ .positionTransform = mat4(),
+ .boundaries = rect,
+ .roundedCornersCrop = rect,
+ .roundedCornersRadius = {50.f, 50.f},
+ },
+ .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
+ .maxLuminanceNits = 1000.f,
+ .usePremultipliedAlpha = true,
+ .isOpaque = false}},
+ .alpha = 0.5f,
+ .sourceDataspace = kOtherDataSpace,
+ };
+
+ for (auto alpha : {0.5f, 1.f}) {
+ layer.alpha = alpha;
+ std::vector<LayerSettings> layers;
+
+ for (auto layerWhitePoint : kLayerWhitePoints) {
+ layer.whitePointNits = layerWhitePoint;
+ layers.push_back(layer);
+ }
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+ }
}
//
@@ -335,7 +630,7 @@ static void drawHolePunchLayer(SkiaRenderEngine* renderengine, const DisplaySett
// kFlushAfterEveryLayer = true
// in external/skia/src/gpu/gl/builders/GrGLShaderStringBuilder.cpp
// gPrintSKSL = true
-void Cache::primeShaderCache(SkiaRenderEngine* renderengine) {
+void Cache::primeShaderCache(SkiaRenderEngine* renderengine, bool shouldPrimeUltraHDR) {
const int previousCount = renderengine->reportShadersCompiled();
if (previousCount) {
ALOGD("%d Shaders already compiled before Cache::primeShaderCache ran\n", previousCount);
@@ -360,6 +655,23 @@ void Cache::primeShaderCache(SkiaRenderEngine* renderengine) {
.maxLuminance = 500,
.outputDataspace = kOtherDataSpace,
};
+ DisplaySettings p3DisplayEnhance{.physicalDisplay = displayRect,
+ .clip = displayRect,
+ .maxLuminance = 500,
+ .outputDataspace = kOtherDataSpace,
+ .dimmingStage = aidl::android::hardware::graphics::
+ composer3::DimmingStage::GAMMA_OETF,
+ .renderIntent = aidl::android::hardware::graphics::
+ composer3::RenderIntent::ENHANCE};
+ DisplaySettings bt2020Display{.physicalDisplay = displayRect,
+ .clip = displayRect,
+ .maxLuminance = 500,
+ .outputDataspace = ui::Dataspace::BT2020,
+ .deviceHandlesColorTransform = true,
+ .dimmingStage = aidl::android::hardware::graphics::composer3::
+ DimmingStage::GAMMA_OETF,
+ .renderIntent = aidl::android::hardware::graphics::composer3::
+ RenderIntent::TONE_MAP_ENHANCE};
const int64_t usage = GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE;
@@ -384,6 +696,8 @@ void Cache::primeShaderCache(SkiaRenderEngine* renderengine) {
impl::ExternalTexture::Usage::WRITEABLE);
drawHolePunchLayer(renderengine, display, dstTexture);
drawSolidLayers(renderengine, display, dstTexture);
+ drawSolidLayers(renderengine, p3Display, dstTexture);
+ drawSolidDimmedLayers(renderengine, display, dstTexture);
drawShadowLayers(renderengine, display, srcTexture);
drawShadowLayers(renderengine, p3Display, srcTexture);
@@ -424,21 +738,45 @@ void Cache::primeShaderCache(SkiaRenderEngine* renderengine) {
for (auto texture : textures) {
drawImageLayers(renderengine, display, dstTexture, texture);
+
+ drawImageDimmedLayers(renderengine, display, dstTexture, texture);
+ drawImageDimmedLayers(renderengine, p3Display, dstTexture, texture);
+ drawImageDimmedLayers(renderengine, bt2020Display, dstTexture, texture);
+
// Draw layers for b/185569240.
drawClippedLayers(renderengine, display, dstTexture, texture);
}
drawPIPImageLayer(renderengine, display, dstTexture, externalTexture);
+ drawTransparentImageDimmedLayers(renderengine, bt2020Display, dstTexture, externalTexture);
+ drawTransparentImageDimmedLayers(renderengine, display, dstTexture, externalTexture);
+ drawTransparentImageDimmedLayers(renderengine, p3Display, dstTexture, externalTexture);
+ drawTransparentImageDimmedLayers(renderengine, p3DisplayEnhance, dstTexture,
+ externalTexture);
+
+ drawClippedDimmedImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
+
+ if (shouldPrimeUltraHDR) {
+ drawBT2020ClippedImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
+
+ drawBT2020ImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
+ drawBT2020ImageLayers(renderengine, p3Display, dstTexture, externalTexture);
+
+ drawExtendedHDRImageLayers(renderengine, display, dstTexture, externalTexture);
+ drawExtendedHDRImageLayers(renderengine, p3Display, dstTexture, externalTexture);
+ drawExtendedHDRImageLayers(renderengine, p3DisplayEnhance, dstTexture, externalTexture);
+
+ drawP3ImageLayers(renderengine, p3DisplayEnhance, dstTexture, externalTexture);
+ }
+
// draw one final layer synchronously to force GL submit
LayerSettings layer{
.source = PixelSource{.solidColor = half3(0.f, 0.f, 0.f)},
};
auto layers = std::vector<LayerSettings>{layer};
// call get() to make it synchronous
- renderengine
- ->drawLayers(display, layers, dstTexture, kUseFrameBufferCache, base::unique_fd())
- .get();
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd()).get();
const nsecs_t timeAfter = systemTime();
const float compileTimeMs = static_cast<float>(timeAfter - timeBefore) / 1.0E6;
diff --git a/libs/renderengine/skia/Cache.h b/libs/renderengine/skia/Cache.h
index 437571e616..62f6705c89 100644
--- a/libs/renderengine/skia/Cache.h
+++ b/libs/renderengine/skia/Cache.h
@@ -22,7 +22,7 @@ class SkiaRenderEngine;
class Cache {
public:
- static void primeShaderCache(SkiaRenderEngine*);
+ static void primeShaderCache(SkiaRenderEngine*, bool shouldPrimeUltraHDR);
private:
Cache() = default;
diff --git a/libs/renderengine/gl/GLExtensions.cpp b/libs/renderengine/skia/GLExtensions.cpp
index b479400a06..1d4d35fb54 100644
--- a/libs/renderengine/gl/GLExtensions.cpp
+++ b/libs/renderengine/skia/GLExtensions.cpp
@@ -23,11 +23,11 @@
#include <stdio.h>
#include <stdlib.h>
-ANDROID_SINGLETON_STATIC_INSTANCE(android::renderengine::gl::GLExtensions)
+ANDROID_SINGLETON_STATIC_INSTANCE(android::renderengine::skia::GLExtensions)
namespace android {
namespace renderengine {
-namespace gl {
+namespace skia {
namespace {
@@ -134,6 +134,6 @@ char const* GLExtensions::getEGLExtensions() const {
return mEGLExtensions.c_str();
}
-} // namespace gl
+} // namespace skia
} // namespace renderengine
} // namespace android
diff --git a/libs/renderengine/gl/GLExtensions.h b/libs/renderengine/skia/GLExtensions.h
index e415ff304a..0cb1bda0df 100644
--- a/libs/renderengine/gl/GLExtensions.h
+++ b/libs/renderengine/skia/GLExtensions.h
@@ -29,7 +29,7 @@
namespace android {
namespace renderengine {
-namespace gl {
+namespace skia {
class GLExtensions : public Singleton<GLExtensions> {
public:
@@ -81,7 +81,7 @@ private:
GLExtensions& operator=(const GLExtensions&);
};
-} // namespace gl
+} // namespace skia
} // namespace renderengine
} // namespace android
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index ff598e7ab5..2053c6a34f 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -24,8 +24,10 @@
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GrContextOptions.h>
+#include <GrTypes.h>
#include <android-base/stringprintf.h>
#include <gl/GrGLInterface.h>
+#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
#include <gui/TraceUtils.h>
#include <sync/sync.h>
#include <ui/DebugUtils.h>
@@ -36,17 +38,26 @@
#include <memory>
#include <numeric>
-#include "../gl/GLExtensions.h"
+#include "GLExtensions.h"
#include "log/log_main.h"
-bool checkGlError(const char* op, int lineNumber);
-
namespace android {
namespace renderengine {
namespace skia {
using base::StringAppendF;
+static bool checkGlError(const char* op, int lineNumber) {
+ bool errorFound = false;
+ GLint error = glGetError();
+ while (error != GL_NO_ERROR) {
+ errorFound = true;
+ error = glGetError();
+ ALOGV("after %s() (line # %d) glError (0x%x)\n", op, lineNumber, error);
+ }
+ return errorFound;
+}
+
static status_t selectConfigForAttribute(EGLDisplay dpy, EGLint const* attrs, EGLint attribute,
EGLint wanted, EGLConfig* outConfig) {
EGLint numConfigs = -1, n = 0;
@@ -149,7 +160,7 @@ std::unique_ptr<SkiaGLRenderEngine> SkiaGLRenderEngine::create(
LOG_ALWAYS_FATAL("eglQueryString(EGL_EXTENSIONS) failed");
}
- auto& extensions = gl::GLExtensions::getInstance();
+ auto& extensions = GLExtensions::getInstance();
extensions.initWithEGLStrings(eglVersion, eglExtensions);
// The code assumes that ES2 or later is available if this extension is
@@ -251,14 +262,13 @@ EGLConfig SkiaGLRenderEngine::chooseEglConfig(EGLDisplay display, int format, bo
SkiaGLRenderEngine::SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display,
EGLContext ctxt, EGLSurface placeholder,
EGLContext protectedContext, EGLSurface protectedPlaceholder)
- : SkiaRenderEngine(args.renderEngineType,
- static_cast<PixelFormat>(args.pixelFormat),
- args.useColorManagement, args.supportsBackgroundBlur),
+ : SkiaRenderEngine(args.renderEngineType, static_cast<PixelFormat>(args.pixelFormat),
+ args.supportsBackgroundBlur),
mEGLDisplay(display),
mEGLContext(ctxt),
mPlaceholderSurface(placeholder),
mProtectedEGLContext(protectedContext),
- mProtectedPlaceholderSurface(protectedPlaceholder) { }
+ mProtectedPlaceholderSurface(protectedPlaceholder) {}
SkiaGLRenderEngine::~SkiaGLRenderEngine() {
finishRenderingAndAbandonContext();
@@ -290,10 +300,10 @@ SkiaRenderEngine::Contexts SkiaGLRenderEngine::createDirectContexts(
LOG_ALWAYS_FATAL_IF(!glInterface.get(), "GrGLMakeNativeInterface() failed");
SkiaRenderEngine::Contexts contexts;
- contexts.first = GrDirectContext::MakeGL(glInterface, options);
+ contexts.first = GrDirectContexts::MakeGL(glInterface, options);
if (supportsProtectedContentImpl()) {
useProtectedContextImpl(GrProtected::kYes);
- contexts.second = GrDirectContext::MakeGL(glInterface, options);
+ contexts.second = GrDirectContexts::MakeGL(glInterface, options);
useProtectedContextImpl(GrProtected::kNo);
}
@@ -330,7 +340,8 @@ base::unique_fd SkiaGLRenderEngine::flushAndSubmit(GrDirectContext* grContext) {
} else {
ATRACE_BEGIN("Submit(sync=false)");
}
- bool success = grContext->submit(requireSync);
+ bool success = grContext->submit(requireSync ? GrSyncCpu::kYes :
+ GrSyncCpu::kNo);
ATRACE_END();
if (!success) {
ALOGE("Failed to flush RenderEngine commands");
@@ -343,8 +354,8 @@ base::unique_fd SkiaGLRenderEngine::flushAndSubmit(GrDirectContext* grContext) {
}
bool SkiaGLRenderEngine::waitGpuFence(base::borrowed_fd fenceFd) {
- if (!gl::GLExtensions::getInstance().hasNativeFenceSync() ||
- !gl::GLExtensions::getInstance().hasWaitSync()) {
+ if (!GLExtensions::getInstance().hasNativeFenceSync() ||
+ !GLExtensions::getInstance().hasWaitSync()) {
return false;
}
@@ -379,7 +390,7 @@ bool SkiaGLRenderEngine::waitGpuFence(base::borrowed_fd fenceFd) {
base::unique_fd SkiaGLRenderEngine::flush() {
ATRACE_CALL();
- if (!gl::GLExtensions::getInstance().hasNativeFenceSync()) {
+ if (!GLExtensions::getInstance().hasNativeFenceSync()) {
return base::unique_fd();
}
@@ -470,13 +481,13 @@ EGLContext SkiaGLRenderEngine::createEglContext(EGLDisplay display, EGLConfig co
std::optional<RenderEngine::ContextPriority> SkiaGLRenderEngine::createContextPriority(
const RenderEngineCreationArgs& args) {
- if (!gl::GLExtensions::getInstance().hasContextPriority()) {
+ if (!GLExtensions::getInstance().hasContextPriority()) {
return std::nullopt;
}
switch (args.contextPriority) {
case RenderEngine::ContextPriority::REALTIME:
- if (gl::GLExtensions::getInstance().hasRealtimePriority()) {
+ if (GLExtensions::getInstance().hasRealtimePriority()) {
return RenderEngine::ContextPriority::REALTIME;
} else {
ALOGI("Realtime priority unsupported, degrading gracefully to high priority");
@@ -520,7 +531,7 @@ int SkiaGLRenderEngine::getContextPriority() {
}
void SkiaGLRenderEngine::appendBackendSpecificInfoToDump(std::string& result) {
- const gl::GLExtensions& extensions = gl::GLExtensions::getInstance();
+ const GLExtensions& extensions = GLExtensions::getInstance();
StringAppendF(&result, "\n ------------RE GLES------------\n");
StringAppendF(&result, "EGL implementation : %s\n", extensions.getEGLVersion());
StringAppendF(&result, "%s\n", extensions.getEGLExtensions());
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index a733fd0b8e..88326e77d7 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -22,6 +22,7 @@
#include <GrBackendSemaphore.h>
#include <GrContextOptions.h>
+#include <GrTypes.h>
#include <SkBlendMode.h>
#include <SkCanvas.h>
#include <SkColor.h>
@@ -54,6 +55,7 @@
#include <android-base/stringprintf.h>
#include <gui/FenceMonitor.h>
#include <gui/TraceUtils.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include <pthread.h>
#include <src/core/SkTraceEventCommon.h>
#include <sync/sync.h>
@@ -241,8 +243,8 @@ namespace skia {
using base::StringAppendF;
-std::future<void> SkiaRenderEngine::primeCache() {
- Cache::primeShaderCache(this);
+std::future<void> SkiaRenderEngine::primeCache(bool shouldPrimeUltraHDR) {
+ Cache::primeShaderCache(this, shouldPrimeUltraHDR);
return {};
}
@@ -268,10 +270,8 @@ void SkiaRenderEngine::setEnableTracing(bool tracingEnabled) {
}
SkiaRenderEngine::SkiaRenderEngine(RenderEngineType type, PixelFormat pixelFormat,
- bool useColorManagement, bool supportsBackgroundBlur)
- : RenderEngine(type),
- mDefaultPixelFormat(pixelFormat),
- mUseColorManagement(useColorManagement) {
+ bool supportsBackgroundBlur)
+ : RenderEngine(type), mDefaultPixelFormat(pixelFormat) {
if (supportsBackgroundBlur) {
ALOGD("Background Blurs Enabled");
mBlurFilter = new KawaseBlurFilter();
@@ -290,12 +290,12 @@ void SkiaRenderEngine::finishRenderingAndAbandonContext() {
}
if (mGrContext) {
- mGrContext->flushAndSubmit(true);
+ mGrContext->flushAndSubmit(GrSyncCpu::kYes);
mGrContext->abandonContext();
}
if (mProtectedGrContext) {
- mProtectedGrContext->flushAndSubmit(true);
+ mProtectedGrContext->flushAndSubmit(GrSyncCpu::kYes);
mProtectedGrContext->abandonContext();
}
}
@@ -308,7 +308,7 @@ void SkiaRenderEngine::useProtectedContext(bool useProtectedContext) {
// release any scratch resources before switching into a new mode
if (getActiveGrContext()) {
- getActiveGrContext()->purgeUnlockedResources(true);
+ getActiveGrContext()->purgeUnlockedResources(GrPurgeResourceOptions::kScratchResourcesOnly);
}
// Backend-specific way to switch to protected context
@@ -400,7 +400,10 @@ void SkiaRenderEngine::mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
// simply match the existing behavior for protected buffers.) We also never cache any
// buffers while in a protected context.
const bool isProtectedBuffer = buffer->getUsage() & GRALLOC_USAGE_PROTECTED;
- if (isProtectedBuffer || isProtected()) {
+ // Don't attempt to map buffers if we're not gpu sampleable. Callers shouldn't send a buffer
+ // over to RenderEngine.
+ const bool isGpuSampleable = buffer->getUsage() & GRALLOC_USAGE_HW_TEXTURE;
+ if (isProtectedBuffer || isProtected() || !isGpuSampleable) {
return;
}
ATRACE_CALL();
@@ -648,8 +651,7 @@ private:
void SkiaRenderEngine::drawLayersInternal(
const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
const DisplaySettings& display, const std::vector<LayerSettings>& layers,
- const std::shared_ptr<ExternalTexture>& buffer, const bool /*useFramebufferCache*/,
- base::unique_fd&& bufferFence) {
+ const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) {
ATRACE_FORMAT("%s for %s", __func__, display.namePlusId.c_str());
std::lock_guard<std::mutex> lock(mRenderingMutex);
@@ -711,9 +713,7 @@ void SkiaRenderEngine::drawLayersInternal(
SkCanvas* canvas = dstCanvas;
SkiaCapture::OffscreenState offscreenCaptureState;
const LayerSettings* blurCompositionLayer = nullptr;
-
- // TODO (b/270314344): Enable blurs in protected context.
- if (mBlurFilter && !mInProtectedContext) {
+ if (mBlurFilter) {
bool requiresCompositionLayer = false;
for (const auto& layer : layers) {
// if the layer doesn't have blur or it is not visible then continue
@@ -807,8 +807,7 @@ void SkiaRenderEngine::drawLayersInternal(
const auto [bounds, roundRectClip] =
getBoundsAndClip(layer.geometry.boundaries, layer.geometry.roundedCornersCrop,
layer.geometry.roundedCornersRadius);
- // TODO (b/270314344): Enable blurs in protected context.
- if (mBlurFilter && layerHasBlur(layer, ctModifiesAlpha) && !mInProtectedContext) {
+ if (mBlurFilter && layerHasBlur(layer, ctModifiesAlpha)) {
std::unordered_map<uint32_t, sk_sp<SkImage>> cachedBlurs;
// if multiple layers have blur, then we need to take a snapshot now because
@@ -922,8 +921,7 @@ void SkiaRenderEngine::drawLayersInternal(
// luminance in linear space, which color pipelines request GAMMA_OETF break
// without a gamma 2.2 fixup.
const bool requiresLinearEffect = layer.colorTransform != mat4() ||
- (mUseColorManagement &&
- needsToneMapping(layer.sourceDataspace, display.outputDataspace)) ||
+ (needsToneMapping(layer.sourceDataspace, display.outputDataspace)) ||
(dimInLinearSpace && !equalsWithinMargin(1.f, layerDimmingRatio)) ||
(!dimInLinearSpace && isExtendedHdr);
@@ -934,10 +932,7 @@ void SkiaRenderEngine::drawLayersInternal(
continue;
}
- // If color management is disabled, then mark the source image with the same colorspace as
- // the destination surface so that Skia's color management is a no-op.
- const ui::Dataspace layerDataspace =
- !mUseColorManagement ? display.outputDataspace : layer.sourceDataspace;
+ const ui::Dataspace layerDataspace = layer.sourceDataspace;
SkPaint paint;
if (layer.source.buffer.buffer) {
@@ -1129,7 +1124,7 @@ void SkiaRenderEngine::drawLayersInternal(
}
if (kFlushAfterEveryLayer) {
ATRACE_NAME("flush surface");
- activeSurface->flush();
+ skgpu::ganesh::Flush(activeSurface);
}
}
for (const auto& borderRenderInfo : display.borderInfoList) {
@@ -1157,7 +1152,7 @@ void SkiaRenderEngine::drawLayersInternal(
{
ATRACE_NAME("flush surface");
LOG_ALWAYS_FATAL_IF(activeSurface != dstSurface);
- activeSurface->flush();
+ skgpu::ganesh::Flush(activeSurface);
}
auto drawFence = sp<Fence>::make(flushAndSubmit(grContext));
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index 723e73c29e..ac134afa64 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -59,23 +59,17 @@ class BlurFilter;
class SkiaRenderEngine : public RenderEngine {
public:
static std::unique_ptr<SkiaRenderEngine> create(const RenderEngineCreationArgs& args);
- SkiaRenderEngine(RenderEngineType type,
- PixelFormat pixelFormat,
- bool useColorManagement,
- bool supportsBackgroundBlur);
+ SkiaRenderEngine(RenderEngineType type, PixelFormat pixelFormat, bool supportsBackgroundBlur);
~SkiaRenderEngine() override;
- std::future<void> primeCache() override final;
+ std::future<void> primeCache(bool shouldPrimeUltraHDR) override final;
void cleanupPostRender() override final;
- void cleanFramebufferCache() override final{ }
bool supportsBackgroundBlur() override final {
return mBlurFilter != nullptr;
}
void onActiveDisplaySizeChanged(ui::Size size) override final;
int reportShadersCompiled();
- virtual void genTextures(size_t /*count*/, uint32_t* /*names*/) override final{};
- virtual void deleteTextures(size_t /*count*/, uint32_t const* /*names*/) override final{};
virtual void setEnableTracing(bool tracingEnabled) override final;
void useProtectedContext(bool useProtectedContext) override;
@@ -142,7 +136,6 @@ private:
const DisplaySettings& display,
const std::vector<LayerSettings>& layers,
const std::shared_ptr<ExternalTexture>& buffer,
- const bool useFramebufferCache,
base::unique_fd&& bufferFence) override final;
void dump(std::string& result) override final;
@@ -162,7 +155,6 @@ private:
sk_sp<SkShader> createRuntimeEffectShader(const RuntimeEffectShaderParameters&);
const PixelFormat mDefaultPixelFormat;
- const bool mUseColorManagement;
// Identifier used for various mappings of layers to various
// textures or shaders
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
index c16586bb6b..ba20d1f223 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -25,6 +25,7 @@
#include <GrContextOptions.h>
#include <vk/GrVkExtensions.h>
#include <vk/GrVkTypes.h>
+#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
#include <android-base/stringprintf.h>
#include <gui/TraceUtils.h>
@@ -432,6 +433,10 @@ VulkanInterface initVulkanInterface(bool protectedContent = false) {
// Looks like this would slow things down and we can't depend on it on all platforms
interface.physicalDeviceFeatures2->features.robustBufferAccess = VK_FALSE;
+ if (protectedContent && !interface.protectedMemoryFeatures->protectedMemory) {
+ BAIL("Protected memory not supported");
+ }
+
float queuePriorities[1] = {0.0f};
void* queueNextPtr = nullptr;
@@ -592,7 +597,7 @@ std::unique_ptr<SkiaVkRenderEngine> SkiaVkRenderEngine::create(
SkiaVkRenderEngine::SkiaVkRenderEngine(const RenderEngineCreationArgs& args)
: SkiaRenderEngine(args.renderEngineType, static_cast<PixelFormat>(args.pixelFormat),
- args.useColorManagement, args.supportsBackgroundBlur) {}
+ args.supportsBackgroundBlur) {}
SkiaVkRenderEngine::~SkiaVkRenderEngine() {
finishRenderingAndAbandonContext();
@@ -603,11 +608,11 @@ SkiaRenderEngine::Contexts SkiaVkRenderEngine::createDirectContexts(
sSetupVulkanInterface();
SkiaRenderEngine::Contexts contexts;
- contexts.first = GrDirectContext::MakeVulkan(sVulkanInterface.getBackendContext(), options);
+ contexts.first = GrDirectContexts::MakeVulkan(sVulkanInterface.getBackendContext(), options);
if (supportsProtectedContentImpl()) {
contexts.second =
- GrDirectContext::MakeVulkan(sProtectedContentVulkanInterface.getBackendContext(),
- options);
+ GrDirectContexts::MakeVulkan(sProtectedContentVulkanInterface.getBackendContext(),
+ options);
}
return contexts;
@@ -681,7 +686,7 @@ base::unique_fd SkiaVkRenderEngine::flushAndSubmit(GrDirectContext* grContext) {
flushInfo.fFinishedContext = destroySemaphoreInfo;
}
GrSemaphoresSubmitted submitted = grContext->flush(flushInfo);
- grContext->submit(false /* no cpu sync */);
+ grContext->submit(GrSyncCpu::kNo);
int drawFenceFd = -1;
if (semaphore != VK_NULL_HANDLE) {
if (GrSemaphoresSubmitted::kYes == submitted) {
diff --git a/libs/renderengine/skia/debug/SkiaCapture.cpp b/libs/renderengine/skia/debug/SkiaCapture.cpp
index b21b01cc1b..48dc77eeec 100644
--- a/libs/renderengine/skia/debug/SkiaCapture.cpp
+++ b/libs/renderengine/skia/debug/SkiaCapture.cpp
@@ -31,14 +31,17 @@
#include "SkRect.h"
#include "SkTypeface.h"
#include "src/utils/SkMultiPictureDocument.h"
+#include <sys/stat.h>
namespace android {
namespace renderengine {
namespace skia {
// The root of the filename to write a recorded SKP to. In order for this file to
-// be written to /data/user/, user must run 'adb shell setenforce 0' on the device.
-static const std::string CAPTURED_FILENAME_BASE = "/data/user/re_skiacapture";
+// be written, user must run 'adb shell setenforce 0' on the device. Note: This
+// is handled by record.sh. FIXME(b/296282988): With updated selinux policies,
+// 'adb shell setenforce 0' should be unnecessary.
+static const std::string CAPTURED_FILE_DIR = "/data/misc/mskps";
SkiaCapture::~SkiaCapture() {
mTimer.stop();
@@ -169,11 +172,12 @@ bool SkiaCapture::setupMultiFrameCapture() {
ATRACE_CALL();
ALOGD("Set up multi-frame capture, ms = %llu", mTimerInterval.count());
base::SetProperty(PROPERTY_DEBUG_RENDERENGINE_CAPTURE_FILENAME, "");
- const std::scoped_lock lock(mMutex);
- // Attach a timestamp to the file.
+ mkdir(CAPTURED_FILE_DIR.c_str(), 0700);
+
+ const std::scoped_lock lock(mMutex);
mCaptureFile.clear();
- base::StringAppendF(&mCaptureFile, "%s_%lld.mskp", CAPTURED_FILENAME_BASE.c_str(),
+ base::StringAppendF(&mCaptureFile, "%s/re_skiacapture_%lld.mskp", CAPTURED_FILE_DIR.c_str(),
std::chrono::steady_clock::now().time_since_epoch().count());
auto stream = std::make_unique<SkFILEWStream>(mCaptureFile.c_str());
// We own this stream and need to hold it until close() finishes.
diff --git a/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp b/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp
index f24a4f1c05..5bf6560cad 100644
--- a/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp
+++ b/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp
@@ -18,7 +18,6 @@
#include "SkiaMemoryReporter.h"
-#include <SkString.h>
#include <android-base/stringprintf.h>
#include <log/log_main.h>
@@ -142,7 +141,7 @@ void SkiaMemoryReporter::logOutput(std::string& log, bool wrappedResources) {
TraceValue traceValue = convertUnits(result->second);
const char* entry = (traceValue.count > 1) ? "entries" : "entry";
StringAppendF(&log, " %s: %.2f %s (%d %s)\n", categoryItem->first.c_str(),
- traceValue.value, traceValue.units, traceValue.count, entry);
+ traceValue.value, traceValue.units.c_str(), traceValue.count, entry);
}
if (mItemize) {
for (const auto& individualItem : resultsMap) {
@@ -153,7 +152,7 @@ void SkiaMemoryReporter::logOutput(std::string& log, bool wrappedResources) {
auto result = individualItem.second.find("size");
TraceValue size = convertUnits(result->second);
StringAppendF(&log, " %s: size[%.2f %s]", individualItem.first.c_str(),
- size.value, size.units);
+ size.value, size.units.c_str());
if (!wrappedResources) {
for (const auto& itemValues : individualItem.second) {
if (strcmp("size", itemValues.first) == 0) {
@@ -162,10 +161,10 @@ void SkiaMemoryReporter::logOutput(std::string& log, bool wrappedResources) {
TraceValue traceValue = convertUnits(itemValues.second);
if (traceValue.value == 0.0f) {
StringAppendF(&log, " %s[%s]", itemValues.first,
- traceValue.units);
+ traceValue.units.c_str());
} else {
StringAppendF(&log, " %s[%.2f %s]", itemValues.first,
- traceValue.value, traceValue.units);
+ traceValue.value, traceValue.units.c_str());
}
}
}
@@ -184,16 +183,16 @@ void SkiaMemoryReporter::logTotals(std::string& log) {
TraceValue total = convertUnits(mTotalSize);
TraceValue purgeable = convertUnits(mPurgeableSize);
StringAppendF(&log, " %.0f bytes, %.2f %s (%.2f %s is purgeable)\n", mTotalSize.value,
- total.value, total.units, purgeable.value, purgeable.units);
+ total.value, total.units.c_str(), purgeable.value, purgeable.units.c_str());
}
SkiaMemoryReporter::TraceValue SkiaMemoryReporter::convertUnits(const TraceValue& value) {
TraceValue output(value);
- if (SkString("bytes") == SkString(output.units) && output.value >= 1024) {
+ if (SkString("bytes") == output.units && output.value >= 1024) {
output.value = output.value / 1024.0f;
output.units = "KB";
}
- if (SkString("KB") == SkString(output.units) && output.value >= 1024) {
+ if (SkString("KB") == output.units && output.value >= 1024) {
output.value = output.value / 1024.0f;
output.units = "MB";
}
diff --git a/libs/renderengine/skia/debug/SkiaMemoryReporter.h b/libs/renderengine/skia/debug/SkiaMemoryReporter.h
index dbbd65b3da..da91674e1f 100644
--- a/libs/renderengine/skia/debug/SkiaMemoryReporter.h
+++ b/libs/renderengine/skia/debug/SkiaMemoryReporter.h
@@ -16,6 +16,7 @@
#pragma once
+#include <SkString.h>
#include <SkTraceMemoryDump.h>
#include <string>
@@ -75,7 +76,7 @@ private:
TraceValue(const char* units, uint64_t value) : units(units), value(value), count(1) {}
TraceValue(const TraceValue& v) : units(v.units), value(v.value), count(v.count) {}
- const char* units;
+ SkString units;
float value;
int count;
};
@@ -104,4 +105,4 @@ private:
} /* namespace skia */
} /* namespace renderengine */
-} /* namespace android */ \ No newline at end of file
+} /* namespace android */
diff --git a/libs/renderengine/skia/debug/record.sh b/libs/renderengine/skia/debug/record.sh
index e99b7ae390..88d8b09bd0 100755
--- a/libs/renderengine/skia/debug/record.sh
+++ b/libs/renderengine/skia/debug/record.sh
@@ -16,7 +16,6 @@ elif [ "$1" == "rootandsetup" ]; then
# first time use requires these changes
adb root
adb shell setenforce 0
- adb shell setprop debug.renderengine.backend "skiaglthreaded"
adb shell stop
adb shell start
exit 1;
@@ -44,7 +43,6 @@ sleep $(($1 / 1000 + 4));
# There is no guarantee that at least one frame passed through renderengine during that time
# but as far as I know it always at least writes a 0-byte file with a new name, unless it crashes
# the process it is recording.
-# /data/user/re_skiacapture_56204430551705.mskp
spin() {
case "$spin" in
diff --git a/libs/renderengine/skia/filters/BlurFilter.cpp b/libs/renderengine/skia/filters/BlurFilter.cpp
index 2557ac9770..1e0c4cf9d0 100644
--- a/libs/renderengine/skia/filters/BlurFilter.cpp
+++ b/libs/renderengine/skia/filters/BlurFilter.cpp
@@ -16,6 +16,7 @@
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
#include "BlurFilter.h"
+#include <SkBlendMode.h>
#include <SkCanvas.h>
#include <SkPaint.h>
#include <SkRRect.h>
@@ -23,6 +24,7 @@
#include <SkSize.h>
#include <SkString.h>
#include <SkSurface.h>
+#include <SkTileMode.h>
#include <log/log.h>
#include <utils/Trace.h>
diff --git a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
index 511d7c9350..e72c501336 100644
--- a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
+++ b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
@@ -17,6 +17,7 @@
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
#include "GaussianBlurFilter.h"
+#include <SkBlendMode.h>
#include <SkCanvas.h>
#include <SkPaint.h>
#include <SkRRect.h>
@@ -25,6 +26,8 @@
#include <SkSize.h>
#include <SkString.h>
#include <SkSurface.h>
+#include <SkTileMode.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include "include/gpu/GpuTypes.h" // from Skia
#include <log/log.h>
#include <utils/Trace.h>
@@ -45,8 +48,8 @@ sk_sp<SkImage> GaussianBlurFilter::generate(GrRecordingContext* context, const u
// Create blur surface with the bit depth and colorspace of the original surface
SkImageInfo scaledInfo = input->imageInfo().makeWH(std::ceil(blurRect.width() * kInputScale),
std::ceil(blurRect.height() * kInputScale));
- sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(context,
- skgpu::Budgeted::kNo, scaledInfo);
+ sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(context,
+ skgpu::Budgeted::kNo, scaledInfo);
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc);
diff --git a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
index e370c39a94..5c9820cdc5 100644
--- a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
+++ b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
@@ -17,13 +17,20 @@
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
#include "KawaseBlurFilter.h"
+#include <SkAlphaType.h>
+#include <SkBlendMode.h>
#include <SkCanvas.h>
+#include <SkImageInfo.h>
#include <SkPaint.h>
#include <SkRRect.h>
#include <SkRuntimeEffect.h>
+#include <SkShader.h>
#include <SkSize.h>
#include <SkString.h>
#include <SkSurface.h>
+#include <SkTileMode.h>
+#include <include/gpu/GpuTypes.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include <log/log.h>
#include <utils/Trace.h>
@@ -32,19 +39,18 @@ namespace renderengine {
namespace skia {
KawaseBlurFilter::KawaseBlurFilter(): BlurFilter() {
- SkString blurString(R"(
- uniform shader child;
- uniform float in_blurOffset;
-
- half4 main(float2 xy) {
- half4 c = child.eval(xy);
- c += child.eval(xy + float2(+in_blurOffset, +in_blurOffset));
- c += child.eval(xy + float2(+in_blurOffset, -in_blurOffset));
- c += child.eval(xy + float2(-in_blurOffset, -in_blurOffset));
- c += child.eval(xy + float2(-in_blurOffset, +in_blurOffset));
- return half4(c.rgb * 0.2, 1.0);
- }
- )");
+ SkString blurString(
+ "uniform shader child;"
+ "uniform float in_blurOffset;"
+
+ "half4 main(float2 xy) {"
+ "half4 c = child.eval(xy);"
+ "c += child.eval(xy + float2(+in_blurOffset, +in_blurOffset));"
+ "c += child.eval(xy + float2(+in_blurOffset, -in_blurOffset));"
+ "c += child.eval(xy + float2(-in_blurOffset, -in_blurOffset));"
+ "c += child.eval(xy + float2(-in_blurOffset, +in_blurOffset));"
+ "return half4(c.rgb * 0.2, 1.0);"
+ "}");
auto [blurEffect, error] = SkRuntimeEffect::MakeForShader(blurString);
if (!blurEffect) {
@@ -53,14 +59,36 @@ KawaseBlurFilter::KawaseBlurFilter(): BlurFilter() {
mBlurEffect = std::move(blurEffect);
}
-sk_sp<SkImage> KawaseBlurFilter::generate(GrRecordingContext* context, const uint32_t blurRadius,
- const sk_sp<SkImage> input, const SkRect& blurRect)
- const {
+// Draws the given runtime shader on a GPU (Ganesh) surface and returns the result as an
+// SkImage.
+static sk_sp<SkImage> makeImage(SkSurface* surface, SkRuntimeShaderBuilder* builder) {
+ sk_sp<SkShader> shader = builder->makeShader(nullptr);
+ if (!shader) {
+ return nullptr;
+ }
+ SkPaint paint;
+ paint.setShader(std::move(shader));
+ paint.setBlendMode(SkBlendMode::kSrc);
+ surface->getCanvas()->drawPaint(paint);
+ return surface->makeImageSnapshot();
+}
+
+sk_sp<SkImage> KawaseBlurFilter::generate(GrRecordingContext* context,
+ const uint32_t blurRadius,
+ const sk_sp<SkImage> input,
+ const SkRect& blurRect) const {
+ LOG_ALWAYS_FATAL_IF(context == nullptr, "%s: Needs GPU context", __func__);
+ LOG_ALWAYS_FATAL_IF(input == nullptr, "%s: Invalid input image", __func__);
+
+ if (blurRadius == 0) {
+ return input;
+ }
+
// Kawase is an approximation of Gaussian, but it behaves differently from it.
// A radius transformation is required for approximating them, and also to introduce
// non-integer steps, necessary to smoothly interpolate large radii.
float tmpRadius = (float)blurRadius / 2.0f;
- float numberOfPasses = std::min(kMaxPasses, (uint32_t)ceil(tmpRadius));
+ uint32_t numberOfPasses = std::min(kMaxPasses, (uint32_t)ceil(tmpRadius));
float radiusByPasses = tmpRadius / (float)numberOfPasses;
// create blur surface with the bit depth and colorspace of the original surface
@@ -80,14 +108,33 @@ sk_sp<SkImage> KawaseBlurFilter::generate(GrRecordingContext* context, const uin
input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear, blurMatrix);
blurBuilder.uniform("in_blurOffset") = radiusByPasses * kInputScale;
- sk_sp<SkImage> tmpBlur(blurBuilder.makeImage(context, nullptr, scaledInfo, false));
+ constexpr int kSampleCount = 1;
+ constexpr bool kMipmapped = false;
+ constexpr SkSurfaceProps* kProps = nullptr;
+ sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(context, skgpu::Budgeted::kYes, scaledInfo,
+ kSampleCount, kTopLeft_GrSurfaceOrigin,
+ kProps, kMipmapped, input->isProtected());
+ LOG_ALWAYS_FATAL_IF(!surface, "%s: Failed to create surface for blurring!", __func__);
+ sk_sp<SkImage> tmpBlur = makeImage(surface.get(), &blurBuilder);
- // And now we'll build our chain of scaled blur stages
- for (auto i = 1; i < numberOfPasses; i++) {
- blurBuilder.child("child") =
- tmpBlur->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear);
- blurBuilder.uniform("in_blurOffset") = (float) i * radiusByPasses * kInputScale;
- tmpBlur = blurBuilder.makeImage(context, nullptr, scaledInfo, false);
+ // And now we'll build our chain of scaled blur stages. If there is more than one pass,
+ // create a second surface and ping pong between them.
+ sk_sp<SkSurface> surfaceTwo;
+ if (numberOfPasses <= 1) {
+ LOG_ALWAYS_FATAL_IF(tmpBlur == nullptr, "%s: tmpBlur is null", __func__);
+ } else {
+ surfaceTwo = surface->makeSurface(scaledInfo);
+ LOG_ALWAYS_FATAL_IF(!surfaceTwo, "%s: Failed to create second blur surface!", __func__);
+
+ for (auto i = 1; i < numberOfPasses; i++) {
+ LOG_ALWAYS_FATAL_IF(tmpBlur == nullptr, "%s: tmpBlur is null for pass %d", __func__, i);
+ blurBuilder.child("child") =
+ tmpBlur->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear);
+ blurBuilder.uniform("in_blurOffset") = (float) i * radiusByPasses * kInputScale;
+ tmpBlur = makeImage(surfaceTwo.get(), &blurBuilder);
+ using std::swap;
+ swap(surface, surfaceTwo);
+ }
}
return tmpBlur;
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index f3f2da8a0e..11d4fdebdc 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -109,7 +109,6 @@ public:
virtual renderengine::RenderEngine::RenderEngineType type() = 0;
virtual std::unique_ptr<renderengine::RenderEngine> createRenderEngine() = 0;
virtual bool typeSupported() = 0;
- virtual bool useColorManagement() const = 0;
};
class SkiaVkRenderEngineFactory : public RenderEngineFactory {
@@ -130,13 +129,11 @@ public:
renderengine::RenderEngineCreationArgs::Builder()
.setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
.setImageCacheSize(1)
- .setUseColorManagerment(false)
.setEnableProtectedContext(false)
.setPrecacheToneMapperShaderOnly(false)
.setSupportsBackgroundBlur(true)
.setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
.setRenderEngineType(type())
- .setUseColorManagerment(useColorManagement())
.build();
return renderengine::skia::SkiaVkRenderEngine::create(reCreationArgs);
}
@@ -144,14 +141,9 @@ public:
bool typeSupported() override {
return skia::SkiaVkRenderEngine::canSupportSkiaVkRenderEngine();
}
- bool useColorManagement() const override { return false; }
void skip() { GTEST_SKIP(); }
};
-class SkiaVkCMRenderEngineFactory : public SkiaVkRenderEngineFactory {
-public:
- bool useColorManagement() const override { return true; }
-};
class SkiaGLESRenderEngineFactory : public RenderEngineFactory {
public:
std::string name() override { return "SkiaGLRenderEngineFactory"; }
@@ -170,13 +162,11 @@ public:
.setSupportsBackgroundBlur(true)
.setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
.setRenderEngineType(type())
- .setUseColorManagerment(useColorManagement())
.build();
return renderengine::skia::SkiaGLRenderEngine::create(reCreationArgs);
}
bool typeSupported() override { return true; }
- bool useColorManagement() const override { return false; }
};
class SkiaGLESCMRenderEngineFactory : public RenderEngineFactory {
@@ -197,13 +187,11 @@ public:
.setSupportsBackgroundBlur(true)
.setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
.setRenderEngineType(type())
- .setUseColorManagerment(useColorManagement())
.build();
return renderengine::skia::SkiaGLRenderEngine::create(reCreationArgs);
}
bool typeSupported() override { return true; }
- bool useColorManagement() const override { return true; }
};
class RenderEngineTest : public ::testing::TestWithParam<std::shared_ptr<RenderEngineFactory>> {
@@ -290,9 +278,6 @@ public:
if (WRITE_BUFFER_TO_FILE_ON_FAILURE && ::testing::Test::HasFailure()) {
writeBufferToFile("/data/texture_out_");
}
- for (uint32_t texName : mTexNames) {
- mRE->deleteTextures(1, &texName);
- }
const ::testing::TestInfo* const test_info =
::testing::UnitTest::GetInstance()->current_test_info();
ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
@@ -418,7 +403,7 @@ public:
}
void expectShadowColor(const renderengine::LayerSettings& castingLayer,
- const renderengine::ShadowSettings& shadow, const ubyte4& casterColor,
+ const ShadowSettings& shadow, const ubyte4& casterColor,
const ubyte4& backgroundColor) {
const Rect casterRect(castingLayer.geometry.boundaries);
Region casterRegion = Region(casterRect);
@@ -458,8 +443,7 @@ public:
backgroundColor.a);
}
- void expectShadowColorWithoutCaster(const FloatRect& casterBounds,
- const renderengine::ShadowSettings& shadow,
+ void expectShadowColorWithoutCaster(const FloatRect& casterBounds, const ShadowSettings& shadow,
const ubyte4& backgroundColor) {
const float shadowInset = shadow.length * -1.0f;
const Rect casterRect(casterBounds);
@@ -478,9 +462,9 @@ public:
backgroundColor.a);
}
- static renderengine::ShadowSettings getShadowSettings(const vec2& casterPos, float shadowLength,
- bool casterIsTranslucent) {
- renderengine::ShadowSettings shadow;
+ static ShadowSettings getShadowSettings(const vec2& casterPos, float shadowLength,
+ bool casterIsTranslucent) {
+ ShadowSettings shadow;
shadow.ambientColor = {0.0f, 0.0f, 0.0f, 0.039f};
shadow.spotColor = {0.0f, 0.0f, 0.0f, 0.19f};
shadow.lightPos = vec3(casterPos.x, casterPos.y, 0);
@@ -505,7 +489,7 @@ public:
void invokeDraw(const renderengine::DisplaySettings& settings,
const std::vector<renderengine::LayerSettings>& layers) {
ftl::Future<FenceResult> future =
- mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd());
+ mRE->drawLayers(settings, layers, mBuffer, base::unique_fd());
ASSERT_TRUE(future.valid());
auto result = future.get();
@@ -617,12 +601,10 @@ public:
void fillGreenColorBufferThenClearRegion();
template <typename SourceVariant>
- void drawShadow(const renderengine::LayerSettings& castingLayer,
- const renderengine::ShadowSettings& shadow, const ubyte4& casterColor,
- const ubyte4& backgroundColor);
+ void drawShadow(const renderengine::LayerSettings& castingLayer, const ShadowSettings& shadow,
+ const ubyte4& casterColor, const ubyte4& backgroundColor);
- void drawShadowWithoutCaster(const FloatRect& castingBounds,
- const renderengine::ShadowSettings& shadow,
+ void drawShadowWithoutCaster(const FloatRect& castingBounds, const ShadowSettings& shadow,
const ubyte4& backgroundColor);
// Tonemaps grey values from sourceDataspace -> Display P3 and checks that GPU and CPU
@@ -635,8 +617,6 @@ public:
std::unique_ptr<renderengine::RenderEngine> mRE;
std::shared_ptr<renderengine::ExternalTexture> mBuffer;
-
- std::vector<uint32_t> mTexNames;
};
void RenderEngineTest::initializeRenderEngine() {
@@ -680,9 +660,6 @@ struct BufferSourceVariant {
static void fillColor(renderengine::LayerSettings& layer, half r, half g, half b,
RenderEngineTest* fixture) {
const auto buf = fixture->allocateSourceBuffer(1, 1);
- uint32_t texName;
- fixture->mRE->genTextures(1, &texName);
- fixture->mTexNames.push_back(texName);
uint8_t* pixels;
buf->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -702,7 +679,6 @@ struct BufferSourceVariant {
buf->getBuffer()->unlock();
layer.source.buffer.buffer = buf;
- layer.source.buffer.textureName = texName;
layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
OpaquenessVariant::setOpaqueBit(layer);
}
@@ -1251,9 +1227,6 @@ void RenderEngineTest::fillRedBufferTextureTransform() {
// Here will allocate a checker board texture, but transform texture
// coordinates so that only the upper left is applied.
const auto buf = allocateSourceBuffer(2, 2);
- uint32_t texName;
- RenderEngineTest::mRE->genTextures(1, &texName);
- this->mTexNames.push_back(texName);
uint8_t* pixels;
buf->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -1274,7 +1247,6 @@ void RenderEngineTest::fillRedBufferTextureTransform() {
buf->getBuffer()->unlock();
layer.source.buffer.buffer = buf;
- layer.source.buffer.textureName = texName;
// Transform coordinates to only be inside the red quadrant.
layer.source.buffer.textureTransform = mat4::scale(vec4(0.2f, 0.2f, 1.f, 1.f));
layer.alpha = 1.0f;
@@ -1300,9 +1272,6 @@ void RenderEngineTest::fillRedBufferWithPremultiplyAlpha() {
renderengine::LayerSettings layer;
const auto buf = allocateSourceBuffer(1, 1);
- uint32_t texName;
- RenderEngineTest::mRE->genTextures(1, &texName);
- this->mTexNames.push_back(texName);
uint8_t* pixels;
buf->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -1314,7 +1283,6 @@ void RenderEngineTest::fillRedBufferWithPremultiplyAlpha() {
buf->getBuffer()->unlock();
layer.source.buffer.buffer = buf;
- layer.source.buffer.textureName = texName;
layer.source.buffer.usePremultipliedAlpha = true;
layer.alpha = 0.5f;
layer.geometry.boundaries = Rect(1, 1).toFloatRect();
@@ -1339,9 +1307,6 @@ void RenderEngineTest::fillRedBufferWithoutPremultiplyAlpha() {
renderengine::LayerSettings layer;
const auto buf = allocateSourceBuffer(1, 1);
- uint32_t texName;
- RenderEngineTest::mRE->genTextures(1, &texName);
- this->mTexNames.push_back(texName);
uint8_t* pixels;
buf->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -1353,7 +1318,6 @@ void RenderEngineTest::fillRedBufferWithoutPremultiplyAlpha() {
buf->getBuffer()->unlock();
layer.source.buffer.buffer = buf;
- layer.source.buffer.textureName = texName;
layer.source.buffer.usePremultipliedAlpha = false;
layer.alpha = 0.5f;
layer.geometry.boundaries = Rect(1, 1).toFloatRect();
@@ -1370,8 +1334,8 @@ void RenderEngineTest::fillBufferWithoutPremultiplyAlpha() {
template <typename SourceVariant>
void RenderEngineTest::drawShadow(const renderengine::LayerSettings& castingLayer,
- const renderengine::ShadowSettings& shadow,
- const ubyte4& casterColor, const ubyte4& backgroundColor) {
+ const ShadowSettings& shadow, const ubyte4& casterColor,
+ const ubyte4& backgroundColor) {
renderengine::DisplaySettings settings;
settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
settings.physicalDisplay = fullscreenRect();
@@ -1407,7 +1371,7 @@ void RenderEngineTest::drawShadow(const renderengine::LayerSettings& castingLaye
}
void RenderEngineTest::drawShadowWithoutCaster(const FloatRect& castingBounds,
- const renderengine::ShadowSettings& shadow,
+ const ShadowSettings& shadow,
const ubyte4& backgroundColor) {
renderengine::DisplaySettings settings;
settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
@@ -1559,9 +1523,7 @@ void RenderEngineTest::tonemap(ui::Dataspace sourceDataspace, std::function<vec3
INSTANTIATE_TEST_SUITE_P(PerRenderEngineType, RenderEngineTest,
testing::Values(std::make_shared<SkiaGLESRenderEngineFactory>(),
- std::make_shared<SkiaGLESCMRenderEngineFactory>(),
- std::make_shared<SkiaVkRenderEngineFactory>(),
- std::make_shared<SkiaVkCMRenderEngineFactory>()));
+ std::make_shared<SkiaVkRenderEngineFactory>()));
TEST_P(RenderEngineTest, drawLayers_noLayersToDraw) {
if (!GetParam()->typeSupported()) {
@@ -1645,8 +1607,7 @@ TEST_P(RenderEngineTest, drawLayers_nullOutputBuffer) {
layer.geometry.boundaries = fullscreenRect().toFloatRect();
BufferSourceVariant<ForceOpaqueBufferVariant>::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
layers.push_back(layer);
- ftl::Future<FenceResult> future =
- mRE->drawLayers(settings, layers, nullptr, true, base::unique_fd());
+ ftl::Future<FenceResult> future = mRE->drawLayers(settings, layers, nullptr, base::unique_fd());
ASSERT_TRUE(future.valid());
auto result = future.get();
@@ -1745,7 +1706,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_colorSource) {
TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_sourceDataspace) {
const auto& renderEngineFactory = GetParam();
// skip for non color management
- if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) {
+ if (!renderEngineFactory->typeSupported()) {
GTEST_SKIP();
}
@@ -1756,7 +1717,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_sourceDataspace) {
TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_outputDataspace) {
const auto& renderEngineFactory = GetParam();
// skip for non color management
- if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) {
+ if (!renderEngineFactory->typeSupported()) {
GTEST_SKIP();
}
@@ -1895,7 +1856,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_opaqueBufferSource)
TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_opaqueBufferSource) {
const auto& renderEngineFactory = GetParam();
// skip for non color management
- if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) {
+ if (!renderEngineFactory->typeSupported()) {
GTEST_SKIP();
}
@@ -1906,7 +1867,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_o
TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_opaqueBufferSource) {
const auto& renderEngineFactory = GetParam();
// skip for non color management
- if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) {
+ if (!renderEngineFactory->typeSupported()) {
GTEST_SKIP();
}
@@ -2045,7 +2006,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_bufferSource) {
TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_bufferSource) {
const auto& renderEngineFactory = GetParam();
// skip for non color management
- if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) {
+ if (!renderEngineFactory->typeSupported()) {
GTEST_SKIP();
}
@@ -2056,7 +2017,7 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_b
TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_bufferSource) {
const auto& renderEngineFactory = GetParam();
// skip for non color management
- if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) {
+ if (!renderEngineFactory->typeSupported()) {
GTEST_SKIP();
}
@@ -2139,9 +2100,8 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_castsWithoutCasterLayer) {
const float shadowLength = 5.0f;
Rect casterBounds(DEFAULT_DISPLAY_WIDTH / 3.0f, DEFAULT_DISPLAY_HEIGHT / 3.0f);
casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
- renderengine::ShadowSettings settings =
- getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
- false /* casterIsTranslucent */);
+ ShadowSettings settings = getShadowSettings(vec2(casterBounds.left, casterBounds.top),
+ shadowLength, false /* casterIsTranslucent */);
drawShadowWithoutCaster(casterBounds.toFloatRect(), settings, backgroundColor);
expectShadowColorWithoutCaster(casterBounds.toFloatRect(), settings, backgroundColor);
@@ -2163,9 +2123,8 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterLayerMinSize) {
renderengine::LayerSettings castingLayer;
castingLayer.geometry.boundaries = casterBounds.toFloatRect();
castingLayer.alpha = 1.0f;
- renderengine::ShadowSettings settings =
- getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
- false /* casterIsTranslucent */);
+ ShadowSettings settings = getShadowSettings(vec2(casterBounds.left, casterBounds.top),
+ shadowLength, false /* casterIsTranslucent */);
drawShadow<ColorSourceVariant>(castingLayer, settings, casterColor, backgroundColor);
expectShadowColor(castingLayer, settings, casterColor, backgroundColor);
@@ -2188,9 +2147,8 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterColorLayer) {
castingLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
castingLayer.geometry.boundaries = casterBounds.toFloatRect();
castingLayer.alpha = 1.0f;
- renderengine::ShadowSettings settings =
- getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
- false /* casterIsTranslucent */);
+ ShadowSettings settings = getShadowSettings(vec2(casterBounds.left, casterBounds.top),
+ shadowLength, false /* casterIsTranslucent */);
drawShadow<ColorSourceVariant>(castingLayer, settings, casterColor, backgroundColor);
expectShadowColor(castingLayer, settings, casterColor, backgroundColor);
@@ -2213,9 +2171,8 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterOpaqueBufferLayer) {
castingLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
castingLayer.geometry.boundaries = casterBounds.toFloatRect();
castingLayer.alpha = 1.0f;
- renderengine::ShadowSettings settings =
- getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
- false /* casterIsTranslucent */);
+ ShadowSettings settings = getShadowSettings(vec2(casterBounds.left, casterBounds.top),
+ shadowLength, false /* casterIsTranslucent */);
drawShadow<BufferSourceVariant<ForceOpaqueBufferVariant>>(castingLayer, settings, casterColor,
backgroundColor);
@@ -2240,9 +2197,8 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterWithRoundedCorner) {
castingLayer.geometry.roundedCornersRadius = {3.0f, 3.0f};
castingLayer.geometry.roundedCornersCrop = casterBounds.toFloatRect();
castingLayer.alpha = 1.0f;
- renderengine::ShadowSettings settings =
- getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
- false /* casterIsTranslucent */);
+ ShadowSettings settings = getShadowSettings(vec2(casterBounds.left, casterBounds.top),
+ shadowLength, false /* casterIsTranslucent */);
drawShadow<BufferSourceVariant<ForceOpaqueBufferVariant>>(castingLayer, settings, casterColor,
backgroundColor);
@@ -2263,9 +2219,8 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_translucentCasterWithAlpha) {
renderengine::LayerSettings castingLayer;
castingLayer.geometry.boundaries = casterBounds.toFloatRect();
castingLayer.alpha = 0.5f;
- renderengine::ShadowSettings settings =
- getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
- true /* casterIsTranslucent */);
+ ShadowSettings settings = getShadowSettings(vec2(casterBounds.left, casterBounds.top),
+ shadowLength, true /* casterIsTranslucent */);
drawShadow<BufferSourceVariant<RelaxOpaqueBufferVariant>>(castingLayer, settings, casterColor,
backgroundColor);
@@ -2298,14 +2253,14 @@ TEST_P(RenderEngineTest, cleanupPostRender_cleansUpOnce) {
layers.push_back(layer);
ftl::Future<FenceResult> futureOne =
- mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd());
+ mRE->drawLayers(settings, layers, mBuffer, base::unique_fd());
ASSERT_TRUE(futureOne.valid());
auto resultOne = futureOne.get();
ASSERT_TRUE(resultOne.ok());
auto fenceOne = resultOne.value();
ftl::Future<FenceResult> futureTwo =
- mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd(fenceOne->dup()));
+ mRE->drawLayers(settings, layers, mBuffer, base::unique_fd(fenceOne->dup()));
ASSERT_TRUE(futureTwo.valid());
auto resultTwo = futureTwo.get();
ASSERT_TRUE(resultTwo.ok());
@@ -2592,10 +2547,6 @@ TEST_P(RenderEngineTest, testBorder) {
GTEST_SKIP();
}
- if (!GetParam()->useColorManagement()) {
- GTEST_SKIP();
- }
-
initializeRenderEngine();
const ui::Dataspace dataspace = ui::Dataspace::V0_SRGB;
@@ -3017,15 +2968,11 @@ TEST_P(RenderEngineTest, test_isOpaque) {
std::vector<renderengine::LayerSettings> layers{greenLayer};
invokeDraw(display, layers);
- if (GetParam()->useColorManagement()) {
- expectBufferColor(rect, 117, 251, 76, 255);
- } else {
- expectBufferColor(rect, 0, 255, 0, 255);
- }
+ expectBufferColor(rect, 117, 251, 76, 255);
}
TEST_P(RenderEngineTest, test_tonemapPQMatches) {
- if (!GetParam()->typeSupported() || !GetParam()->useColorManagement()) {
+ if (!GetParam()->typeSupported()) {
GTEST_SKIP();
}
@@ -3042,7 +2989,7 @@ TEST_P(RenderEngineTest, test_tonemapPQMatches) {
}
TEST_P(RenderEngineTest, test_tonemapHLGMatches) {
- if (!GetParam()->typeSupported() || !GetParam()->useColorManagement()) {
+ if (!GetParam()->typeSupported()) {
GTEST_SKIP();
}
@@ -3257,14 +3204,14 @@ TEST_P(RenderEngineTest, primeShaderCache) {
}
initializeRenderEngine();
- auto fut = mRE->primeCache();
+ auto fut = mRE->primeCache(false);
if (fut.valid()) {
fut.wait();
}
- const int minimumExpectedShadersCompiled = GetParam()->useColorManagement() ? 60 : 30;
+ static constexpr int kMinimumExpectedShadersCompiled = 60;
ASSERT_GT(static_cast<skia::SkiaGLRenderEngine*>(mRE.get())->reportShadersCompiled(),
- minimumExpectedShadersCompiled);
+ kMinimumExpectedShadersCompiled);
}
} // namespace renderengine
} // namespace android
diff --git a/libs/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
index fe3a16d4bf..1b9adba063 100644
--- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp
+++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
@@ -36,7 +36,7 @@ struct RenderEngineThreadedTest : public ::testing::Test {
void SetUp() override {
mThreadedRE = renderengine::threaded::RenderEngineThreaded::create(
[this]() { return std::unique_ptr<renderengine::RenderEngine>(mRenderEngine); },
- renderengine::RenderEngine::RenderEngineType::THREADED);
+ renderengine::RenderEngine::RenderEngineType::SKIA_GL_THREADED);
}
std::unique_ptr<renderengine::threaded::RenderEngineThreaded> mThreadedRE;
@@ -50,25 +50,13 @@ TEST_F(RenderEngineThreadedTest, dump) {
}
TEST_F(RenderEngineThreadedTest, primeCache) {
- EXPECT_CALL(*mRenderEngine, primeCache());
- mThreadedRE->primeCache();
+ EXPECT_CALL(*mRenderEngine, primeCache(false));
+ mThreadedRE->primeCache(false);
// need to call ANY synchronous function after primeCache to ensure that primeCache has
// completed asynchronously before the test completes execution.
mThreadedRE->getContextPriority();
}
-TEST_F(RenderEngineThreadedTest, genTextures) {
- uint32_t texName;
- EXPECT_CALL(*mRenderEngine, genTextures(1, &texName));
- mThreadedRE->genTextures(1, &texName);
-}
-
-TEST_F(RenderEngineThreadedTest, deleteTextures) {
- uint32_t texName;
- EXPECT_CALL(*mRenderEngine, deleteTextures(1, &texName));
- mThreadedRE->deleteTextures(1, &texName);
-}
-
TEST_F(RenderEngineThreadedTest, getMaxTextureSize_returns20) {
size_t size = 20;
EXPECT_CALL(*mRenderEngine, getMaxTextureSize()).WillOnce(Return(size));
@@ -110,7 +98,6 @@ TEST_F(RenderEngineThreadedTest, supportsProtectedContent_returnsTrue) {
}
TEST_F(RenderEngineThreadedTest, PostRenderCleanup_skipped) {
- EXPECT_CALL(*mRenderEngine, canSkipPostRenderCleanup()).WillOnce(Return(true));
EXPECT_CALL(*mRenderEngine, cleanupPostRender()).Times(0);
mThreadedRE->cleanupPostRender();
@@ -119,8 +106,25 @@ TEST_F(RenderEngineThreadedTest, PostRenderCleanup_skipped) {
}
TEST_F(RenderEngineThreadedTest, PostRenderCleanup_notSkipped) {
- EXPECT_CALL(*mRenderEngine, canSkipPostRenderCleanup()).WillOnce(Return(false));
+ renderengine::DisplaySettings settings;
+ std::vector<renderengine::LayerSettings> layers;
+ std::shared_ptr<renderengine::ExternalTexture> buffer = std::make_shared<
+ renderengine::impl::
+ ExternalTexture>(sp<GraphicBuffer>::make(), *mRenderEngine,
+ renderengine::impl::ExternalTexture::Usage::READABLE |
+ renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+ base::unique_fd bufferFence;
+
+ EXPECT_CALL(*mRenderEngine, useProtectedContext(false));
+ EXPECT_CALL(*mRenderEngine, drawLayersInternal)
+ .WillOnce([&](const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
+ const renderengine::DisplaySettings&,
+ const std::vector<renderengine::LayerSettings>&,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); });
EXPECT_CALL(*mRenderEngine, cleanupPostRender()).WillOnce(Return());
+ ftl::Future<FenceResult> future =
+ mThreadedRE->drawLayers(settings, layers, buffer, std::move(bufferFence));
mThreadedRE->cleanupPostRender();
// call ANY synchronous function to ensure that cleanupPostRender has completed.
@@ -155,11 +159,11 @@ TEST_F(RenderEngineThreadedTest, drawLayers) {
.WillOnce([&](const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
const renderengine::DisplaySettings&,
const std::vector<renderengine::LayerSettings>&,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); });
ftl::Future<FenceResult> future =
- mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence));
+ mThreadedRE->drawLayers(settings, layers, buffer, std::move(bufferFence));
ASSERT_TRUE(future.valid());
auto result = future.get();
ASSERT_TRUE(result.ok());
@@ -188,11 +192,11 @@ TEST_F(RenderEngineThreadedTest, drawLayers_protectedLayer) {
.WillOnce([&](const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
const renderengine::DisplaySettings&,
const std::vector<renderengine::LayerSettings>&,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); });
ftl::Future<FenceResult> future =
- mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence));
+ mThreadedRE->drawLayers(settings, layers, buffer, std::move(bufferFence));
ASSERT_TRUE(future.valid());
auto result = future.get();
ASSERT_TRUE(result.ok());
@@ -216,11 +220,11 @@ TEST_F(RenderEngineThreadedTest, drawLayers_protectedOutputBuffer) {
.WillOnce([&](const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
const renderengine::DisplaySettings&,
const std::vector<renderengine::LayerSettings>&,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); });
ftl::Future<FenceResult> future =
- mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence));
+ mThreadedRE->drawLayers(settings, layers, buffer, std::move(bufferFence));
ASSERT_TRUE(future.valid());
auto result = future.get();
ASSERT_TRUE(result.ok());
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index 6a1561abcd..367bee89f9 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -27,8 +27,6 @@
#include <processgroup/processgroup.h>
#include <utils/Trace.h>
-#include "gl/GLESRenderEngine.h"
-
using namespace std::chrono_literals;
namespace android {
@@ -131,7 +129,7 @@ void RenderEngineThreaded::waitUntilInitialized() const {
mInitializedCondition.wait(lock, [=] { return mIsInitialized; });
}
-std::future<void> RenderEngineThreaded::primeCache() {
+std::future<void> RenderEngineThreaded::primeCache(bool shouldPrimeUltraHDR) {
const auto resultPromise = std::make_shared<std::promise<void>>();
std::future<void> resultFuture = resultPromise->get_future();
ATRACE_CALL();
@@ -139,19 +137,20 @@ std::future<void> RenderEngineThreaded::primeCache() {
// for the futures.
{
std::lock_guard lock(mThreadMutex);
- mFunctionCalls.push([resultPromise](renderengine::RenderEngine& instance) {
- ATRACE_NAME("REThreaded::primeCache");
- if (setSchedFifo(false) != NO_ERROR) {
- ALOGW("Couldn't set SCHED_OTHER for primeCache");
- }
-
- instance.primeCache();
- resultPromise->set_value();
-
- if (setSchedFifo(true) != NO_ERROR) {
- ALOGW("Couldn't set SCHED_FIFO for primeCache");
- }
- });
+ mFunctionCalls.push(
+ [resultPromise, shouldPrimeUltraHDR](renderengine::RenderEngine& instance) {
+ ATRACE_NAME("REThreaded::primeCache");
+ if (setSchedFifo(false) != NO_ERROR) {
+ ALOGW("Couldn't set SCHED_OTHER for primeCache");
+ }
+
+ instance.primeCache(shouldPrimeUltraHDR);
+ resultPromise->set_value();
+
+ if (setSchedFifo(true) != NO_ERROR) {
+ ALOGW("Couldn't set SCHED_FIFO for primeCache");
+ }
+ });
}
mCondition.notify_one();
@@ -175,46 +174,6 @@ void RenderEngineThreaded::dump(std::string& result) {
result.assign(resultFuture.get());
}
-void RenderEngineThreaded::genTextures(size_t count, uint32_t* names) {
- ATRACE_CALL();
- // This is a no-op in SkiaRenderEngine.
- if (getRenderEngineType() != RenderEngineType::THREADED) {
- return;
- }
- std::promise<void> resultPromise;
- std::future<void> resultFuture = resultPromise.get_future();
- {
- std::lock_guard lock(mThreadMutex);
- mFunctionCalls.push([&resultPromise, count, names](renderengine::RenderEngine& instance) {
- ATRACE_NAME("REThreaded::genTextures");
- instance.genTextures(count, names);
- resultPromise.set_value();
- });
- }
- mCondition.notify_one();
- resultFuture.wait();
-}
-
-void RenderEngineThreaded::deleteTextures(size_t count, uint32_t const* names) {
- ATRACE_CALL();
- // This is a no-op in SkiaRenderEngine.
- if (getRenderEngineType() != RenderEngineType::THREADED) {
- return;
- }
- std::promise<void> resultPromise;
- std::future<void> resultFuture = resultPromise.get_future();
- {
- std::lock_guard lock(mThreadMutex);
- mFunctionCalls.push([&resultPromise, count, &names](renderengine::RenderEngine& instance) {
- ATRACE_NAME("REThreaded::deleteTextures");
- instance.deleteTextures(count, names);
- resultPromise.set_value();
- });
- }
- mCondition.notify_one();
- resultFuture.wait();
-}
-
void RenderEngineThreaded::mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
bool isRenderable) {
ATRACE_CALL();
@@ -273,60 +232,45 @@ void RenderEngineThreaded::cleanupPostRender() {
ATRACE_NAME("REThreaded::cleanupPostRender");
instance.cleanupPostRender();
});
+ mNeedsPostRenderCleanup = false;
}
mCondition.notify_one();
}
bool RenderEngineThreaded::canSkipPostRenderCleanup() const {
- waitUntilInitialized();
- return mRenderEngine->canSkipPostRenderCleanup();
+ return !mNeedsPostRenderCleanup;
}
void RenderEngineThreaded::drawLayersInternal(
const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
const DisplaySettings& display, const std::vector<LayerSettings>& layers,
- const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
- base::unique_fd&& bufferFence) {
+ const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) {
resultPromise->set_value(Fence::NO_FENCE);
return;
}
ftl::Future<FenceResult> RenderEngineThreaded::drawLayers(
const DisplaySettings& display, const std::vector<LayerSettings>& layers,
- const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
- base::unique_fd&& bufferFence) {
+ const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) {
ATRACE_CALL();
const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
std::future<FenceResult> resultFuture = resultPromise->get_future();
int fd = bufferFence.release();
{
std::lock_guard lock(mThreadMutex);
- mFunctionCalls.push([resultPromise, display, layers, buffer, useFramebufferCache,
- fd](renderengine::RenderEngine& instance) {
- ATRACE_NAME("REThreaded::drawLayers");
- instance.updateProtectedContext(layers, buffer);
- instance.drawLayersInternal(std::move(resultPromise), display, layers, buffer,
- useFramebufferCache, base::unique_fd(fd));
- });
+ mNeedsPostRenderCleanup = true;
+ mFunctionCalls.push(
+ [resultPromise, display, layers, buffer, fd](renderengine::RenderEngine& instance) {
+ ATRACE_NAME("REThreaded::drawLayers");
+ instance.updateProtectedContext(layers, buffer);
+ instance.drawLayersInternal(std::move(resultPromise), display, layers, buffer,
+ base::unique_fd(fd));
+ });
}
mCondition.notify_one();
return resultFuture;
}
-void RenderEngineThreaded::cleanFramebufferCache() {
- ATRACE_CALL();
- // This function is designed so it can run asynchronously, so we do not need to wait
- // for the futures.
- {
- std::lock_guard lock(mThreadMutex);
- mFunctionCalls.push([](renderengine::RenderEngine& instance) {
- ATRACE_NAME("REThreaded::cleanFramebufferCache");
- instance.cleanFramebufferCache();
- });
- }
- mCondition.notify_one();
-}
-
int RenderEngineThreaded::getContextPriority() {
std::promise<int> resultPromise;
std::future<int> resultFuture = resultPromise.get_future();
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index 6eb108e064..74af2bd776 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -42,12 +42,10 @@ public:
RenderEngineThreaded(CreateInstanceFactory factory, RenderEngineType type);
~RenderEngineThreaded() override;
- std::future<void> primeCache() override;
+ std::future<void> primeCache(bool shouldPrimeUltraHDR) override;
void dump(std::string& result) override;
- void genTextures(size_t count, uint32_t* names) override;
- void deleteTextures(size_t count, uint32_t const* names) override;
size_t getMaxTextureSize() const override;
size_t getMaxViewportDims() const override;
@@ -57,10 +55,8 @@ public:
ftl::Future<FenceResult> drawLayers(const DisplaySettings& display,
const std::vector<LayerSettings>& layers,
const std::shared_ptr<ExternalTexture>& buffer,
- const bool useFramebufferCache,
base::unique_fd&& bufferFence) override;
- void cleanFramebufferCache() override;
int getContextPriority() override;
bool supportsBackgroundBlur() override;
void onActiveDisplaySizeChanged(ui::Size size) override;
@@ -75,7 +71,7 @@ protected:
const DisplaySettings& display,
const std::vector<LayerSettings>& layers,
const std::shared_ptr<ExternalTexture>& buffer,
- const bool useFramebufferCache, base::unique_fd&& bufferFence) override;
+ base::unique_fd&& bufferFence) override;
private:
void threadMain(CreateInstanceFactory factory);
@@ -93,6 +89,7 @@ private:
mutable std::mutex mThreadMutex;
std::thread mThread GUARDED_BY(mThreadMutex);
std::atomic<bool> mRunning = true;
+ std::atomic<bool> mNeedsPostRenderCleanup = false;
using Work = std::function<void(renderengine::RenderEngine&)>;
mutable std::queue<Work> mFunctionCalls GUARDED_BY(mThreadMutex);
diff --git a/libs/sensor/Android.bp b/libs/sensor/Android.bp
index b6b9cc4099..d992aa5105 100644
--- a/libs/sensor/Android.bp
+++ b/libs/sensor/Android.bp
@@ -49,6 +49,7 @@ cc_library {
"liblog",
"libhardware",
"libpermission",
+ "android.companion.virtual.virtualdevice_aidl-cpp",
],
export_include_dirs: ["include"],
diff --git a/libs/sensor/ISensorServer.cpp b/libs/sensor/ISensorServer.cpp
index 019d6cb070..12f600e97e 100644
--- a/libs/sensor/ISensorServer.cpp
+++ b/libs/sensor/ISensorServer.cpp
@@ -43,6 +43,8 @@ enum {
CREATE_SENSOR_DIRECT_CONNECTION,
SET_OPERATION_PARAMETER,
GET_RUNTIME_SENSOR_LIST,
+ ENABLE_REPLAY_DATA_INJECTION,
+ ENABLE_HAL_BYPASS_REPLAY_DATA_INJECTION,
};
class BpSensorServer : public BpInterface<ISensorServer>
@@ -64,6 +66,14 @@ public:
Sensor s;
Vector<Sensor> v;
uint32_t n = reply.readUint32();
+ // The size of the n Sensor elements on the wire is what we really want, but
+ // this is better than nothing.
+ if (n > reply.dataAvail()) {
+ ALOGE("Failed to get a reasonable size of the sensor list. This is likely a "
+ "malformed reply parcel. Number of elements: %d, data available in reply: %zu",
+ n, reply.dataAvail());
+ return v;
+ }
v.setCapacity(n);
while (n) {
n--;
@@ -86,6 +96,14 @@ public:
Sensor s;
Vector<Sensor> v;
uint32_t n = reply.readUint32();
+ // The size of the n Sensor elements on the wire is what we really want, but
+ // this is better than nothing.
+ if (n > reply.dataAvail()) {
+ ALOGE("Failed to get a reasonable size of the sensor list. This is likely a "
+ "malformed reply parcel. Number of elements: %d, data available in reply: %zu",
+ n, reply.dataAvail());
+ return v;
+ }
v.setCapacity(n);
while (n) {
n--;
@@ -109,6 +127,14 @@ public:
Sensor s;
Vector<Sensor> v;
uint32_t n = reply.readUint32();
+ // The size of the n Sensor elements on the wire is what we really want, but
+ // this is better than nothing.
+ if (n > reply.dataAvail()) {
+ ALOGE("Failed to get a reasonable size of the sensor list. This is likely a "
+ "malformed reply parcel. Number of elements: %d, data available in reply: %zu",
+ n, reply.dataAvail());
+ return v;
+ }
v.setCapacity(n);
while (n) {
n--;
@@ -138,6 +164,20 @@ public:
return reply.readInt32();
}
+ virtual int isReplayDataInjectionEnabled() {
+ Parcel data, reply;
+ data.writeInterfaceToken(ISensorServer::getInterfaceDescriptor());
+ remote()->transact(ENABLE_REPLAY_DATA_INJECTION, data, &reply);
+ return reply.readInt32();
+ }
+
+ virtual int isHalBypassReplayDataInjectionEnabled() {
+ Parcel data, reply;
+ data.writeInterfaceToken(ISensorServer::getInterfaceDescriptor());
+ remote()->transact(ENABLE_HAL_BYPASS_REPLAY_DATA_INJECTION, data, &reply);
+ return reply.readInt32();
+ }
+
virtual sp<ISensorEventConnection> createSensorDirectConnection(const String16& opPackageName,
int deviceId, uint32_t size, int32_t type, int32_t format,
const native_handle_t *resource) {
@@ -213,6 +253,18 @@ status_t BnSensorServer::onTransact(
reply->writeInt32(static_cast<int32_t>(ret));
return NO_ERROR;
}
+ case ENABLE_REPLAY_DATA_INJECTION: {
+ CHECK_INTERFACE(ISensorServer, data, reply);
+ int32_t ret = isReplayDataInjectionEnabled();
+ reply->writeInt32(static_cast<int32_t>(ret));
+ return NO_ERROR;
+ }
+ case ENABLE_HAL_BYPASS_REPLAY_DATA_INJECTION: {
+ CHECK_INTERFACE(ISensorServer, data, reply);
+ int32_t ret = isHalBypassReplayDataInjectionEnabled();
+ reply->writeInt32(static_cast<int32_t>(ret));
+ return NO_ERROR;
+ }
case GET_DYNAMIC_SENSOR_LIST: {
CHECK_INTERFACE(ISensorServer, data, reply);
const String16& opPackageName = data.readString16();
diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp
index 980f8d16d2..d112a1265c 100644
--- a/libs/sensor/SensorManager.cpp
+++ b/libs/sensor/SensorManager.cpp
@@ -26,6 +26,8 @@
#include <utils/RefBase.h>
#include <utils/Singleton.h>
+#include <android/companion/virtualnative/IVirtualDeviceManagerNative.h>
+
#include <binder/IBinder.h>
#include <binder/IPermissionController.h>
#include <binder/IServiceManager.h>
@@ -39,6 +41,43 @@
namespace android {
// ----------------------------------------------------------------------------
+namespace {
+
+using ::android::companion::virtualnative::IVirtualDeviceManagerNative;
+
+static constexpr int DEVICE_ID_DEFAULT = 0;
+
+// Returns the deviceId of the device where this uid is observed. If the uid is present on more than
+// one devices, return the default deviceId.
+int getDeviceIdForUid(uid_t uid) {
+ sp<IBinder> binder =
+ defaultServiceManager()->checkService(String16("virtualdevice_native"));
+ if (binder != nullptr) {
+ auto vdm = interface_cast<IVirtualDeviceManagerNative>(binder);
+ std::vector<int> deviceIds;
+ vdm->getDeviceIdsForUid(uid, &deviceIds);
+ // If the UID is associated with multiple virtual devices, use the default device's
+ // sensors as we cannot disambiguate here. This effectively means that the app has
+ // activities on different devices at the same time, so it must handle the device
+ // awareness by itself.
+ if (deviceIds.size() == 1) {
+ const int deviceId = deviceIds.at(0);
+ int devicePolicy = IVirtualDeviceManagerNative::DEVICE_POLICY_DEFAULT;
+ vdm->getDevicePolicy(deviceId,
+ IVirtualDeviceManagerNative::POLICY_TYPE_SENSORS,
+ &devicePolicy);
+ if (devicePolicy == IVirtualDeviceManagerNative::DEVICE_POLICY_CUSTOM) {
+ return deviceId;
+ }
+ }
+ } else {
+ ALOGW("Cannot get virtualdevice_native service");
+ }
+ return DEVICE_ID_DEFAULT;
+}
+
+} // namespace
+
Mutex SensorManager::sLock;
std::map<String16, SensorManager*> SensorManager::sPackageInstances;
@@ -53,6 +92,7 @@ SensorManager& SensorManager::getInstanceForPackage(const String16& packageName)
sensorManager = iterator->second;
} else {
String16 opPackageName = packageName;
+ const uid_t uid = IPCThreadState::self()->getCallingUid();
// It is possible that the calling code has no access to the package name.
// In this case we will get the packages for the calling UID and pick the
@@ -63,7 +103,6 @@ SensorManager& SensorManager::getInstanceForPackage(const String16& packageName)
if (opPackageName.size() <= 0) {
sp<IBinder> binder = defaultServiceManager()->getService(String16("permission"));
if (binder != nullptr) {
- const uid_t uid = IPCThreadState::self()->getCallingUid();
Vector<String16> packages;
interface_cast<IPermissionController>(binder)->getPackagesForUid(uid, packages);
if (!packages.isEmpty()) {
@@ -76,7 +115,10 @@ SensorManager& SensorManager::getInstanceForPackage(const String16& packageName)
}
}
- sensorManager = new SensorManager(opPackageName);
+ // Check if the calling UID is observed on a virtual device. If so, provide that device's
+ // sensors by default instead of the default device's sensors.
+ const int deviceId = getDeviceIdForUid(uid);
+ sensorManager = new SensorManager(opPackageName, deviceId);
// If we had no package name, we looked it up from the UID and the sensor
// manager instance we created should also be mapped to the empty package
@@ -102,8 +144,9 @@ void SensorManager::removeInstanceForPackage(const String16& packageName) {
}
}
-SensorManager::SensorManager(const String16& opPackageName)
- : mSensorList(nullptr), mOpPackageName(opPackageName), mDirectConnectionHandle(1) {
+SensorManager::SensorManager(const String16& opPackageName, int deviceId)
+ : mSensorList(nullptr), mOpPackageName(opPackageName), mDeviceId(deviceId),
+ mDirectConnectionHandle(1) {
Mutex::Autolock _l(mLock);
assertStateLocked();
}
@@ -174,7 +217,12 @@ status_t SensorManager::assertStateLocked() {
mDeathObserver = new DeathObserver(*const_cast<SensorManager *>(this));
IInterface::asBinder(mSensorServer)->linkToDeath(mDeathObserver);
- mSensors = mSensorServer->getSensorList(mOpPackageName);
+ if (mDeviceId == DEVICE_ID_DEFAULT) {
+ mSensors = mSensorServer->getSensorList(mOpPackageName);
+ } else {
+ mSensors = mSensorServer->getRuntimeSensorList(mOpPackageName, mDeviceId);
+ }
+
size_t count = mSensors.size();
// If count is 0, mSensorList will be non-null. This is old
// existing behavior and callers expect this.
@@ -310,6 +358,22 @@ bool SensorManager::isDataInjectionEnabled() {
return false;
}
+bool SensorManager::isReplayDataInjectionEnabled() {
+ Mutex::Autolock _l(mLock);
+ if (assertStateLocked() == NO_ERROR) {
+ return mSensorServer->isReplayDataInjectionEnabled();
+ }
+ return false;
+}
+
+bool SensorManager::isHalBypassReplayDataInjectionEnabled() {
+ Mutex::Autolock _l(mLock);
+ if (assertStateLocked() == NO_ERROR) {
+ return mSensorServer->isHalBypassReplayDataInjectionEnabled();
+ }
+ return false;
+}
+
int SensorManager::createDirectChannel(
size_t size, int channelType, const native_handle_t *resourceHandle) {
static constexpr int DEFAULT_DEVICE_ID = 0;
diff --git a/libs/sensor/include/sensor/ISensorServer.h b/libs/sensor/include/sensor/ISensorServer.h
index 58157280b1..00bc1d68b8 100644
--- a/libs/sensor/include/sensor/ISensorServer.h
+++ b/libs/sensor/include/sensor/ISensorServer.h
@@ -48,6 +48,8 @@ public:
virtual sp<ISensorEventConnection> createSensorEventConnection(const String8& packageName,
int mode, const String16& opPackageName, const String16& attributionTag) = 0;
virtual int32_t isDataInjectionEnabled() = 0;
+ virtual int32_t isReplayDataInjectionEnabled() = 0;
+ virtual int32_t isHalBypassReplayDataInjectionEnabled() = 0;
virtual sp<ISensorEventConnection> createSensorDirectConnection(const String16& opPackageName,
int deviceId, uint32_t size, int32_t type, int32_t format,
diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h
index bb44cb8869..e67fac7617 100644
--- a/libs/sensor/include/sensor/SensorManager.h
+++ b/libs/sensor/include/sensor/SensorManager.h
@@ -65,6 +65,8 @@ public:
sp<SensorEventQueue> createEventQueue(
String8 packageName = String8(""), int mode = 0, String16 attributionTag = String16(""));
bool isDataInjectionEnabled();
+ bool isReplayDataInjectionEnabled();
+ bool isHalBypassReplayDataInjectionEnabled();
int createDirectChannel(size_t size, int channelType, const native_handle_t *channelData);
int createDirectChannel(
int deviceId, size_t size, int channelType, const native_handle_t *channelData);
@@ -77,7 +79,7 @@ private:
void sensorManagerDied();
static status_t waitForSensorService(sp<ISensorServer> *server);
- explicit SensorManager(const String16& opPackageName);
+ explicit SensorManager(const String16& opPackageName, int deviceId);
status_t assertStateLocked();
private:
@@ -92,6 +94,7 @@ private:
Vector<Sensor> mDynamicSensors;
sp<IBinder::DeathRecipient> mDeathObserver;
const String16 mOpPackageName;
+ const int mDeviceId;
std::unordered_map<int, sp<ISensorEventConnection>> mDirectConnection;
int32_t mDirectConnectionHandle;
};
diff --git a/libs/sensorprivacy/SensorPrivacyManager.cpp b/libs/sensorprivacy/SensorPrivacyManager.cpp
index 2be98e7281..57c74ee565 100644
--- a/libs/sensorprivacy/SensorPrivacyManager.cpp
+++ b/libs/sensorprivacy/SensorPrivacyManager.cpp
@@ -32,27 +32,12 @@ SensorPrivacyManager::SensorPrivacyManager()
sp<hardware::ISensorPrivacyManager> SensorPrivacyManager::getService()
{
std::lock_guard<Mutex> scoped_lock(mLock);
- int64_t startTime = 0;
sp<hardware::ISensorPrivacyManager> service = mService;
- while (service == nullptr || !IInterface::asBinder(service)->isBinderAlive()) {
- sp<IBinder> binder = defaultServiceManager()->checkService(String16("sensor_privacy"));
- if (binder == nullptr) {
- // Wait for the sensor privacy service to come back...
- if (startTime == 0) {
- startTime = uptimeMillis();
- ALOGI("Waiting for sensor privacy service");
- } else if ((uptimeMillis() - startTime) > 1000000) {
- ALOGW("Waiting too long for sensor privacy service, giving up");
- service = nullptr;
- break;
- }
- usleep(25000);
- } else {
- service = interface_cast<hardware::ISensorPrivacyManager>(binder);
- mService = service;
- }
+ if (service == nullptr || !IInterface::asBinder(service)->isBinderAlive()) {
+ sp<IBinder> binder = defaultServiceManager()->waitForService(String16("sensor_privacy"));
+ mService = interface_cast<hardware::ISensorPrivacyManager>(binder);
}
- return service;
+ return mService;
}
bool SensorPrivacyManager::supportsSensorToggle(int toggleType, int sensor) {
diff --git a/libs/ui/FenceTime.cpp b/libs/ui/FenceTime.cpp
index 538c1d2a42..4246c40f64 100644
--- a/libs/ui/FenceTime.cpp
+++ b/libs/ui/FenceTime.cpp
@@ -363,9 +363,9 @@ void FenceToFenceTimeMap::signalAllForTest(
}
void FenceToFenceTimeMap::garbageCollectLocked() {
- for (auto& it : mMap) {
+ for (auto it = mMap.begin(); it != mMap.end();) {
// Erase all expired weak pointers from the vector.
- auto& vect = it.second;
+ auto& vect = it->second;
vect.erase(
std::remove_if(vect.begin(), vect.end(),
[](const std::weak_ptr<FenceTime>& ft) {
@@ -375,7 +375,9 @@ void FenceToFenceTimeMap::garbageCollectLocked() {
// Also erase the map entry if the vector is now empty.
if (vect.empty()) {
- mMap.erase(it.first);
+ it = mMap.erase(it);
+ } else {
+ it++;
}
}
}
diff --git a/libs/ui/Gralloc5.cpp b/libs/ui/Gralloc5.cpp
index c3b2d3d808..2ec6d18fda 100644
--- a/libs/ui/Gralloc5.cpp
+++ b/libs/ui/Gralloc5.cpp
@@ -31,10 +31,15 @@ using namespace aidl::android::hardware::graphics::allocator;
using namespace aidl::android::hardware::graphics::common;
using namespace ::android::hardware::graphics::mapper;
+using ADataspace = aidl::android::hardware::graphics::common::Dataspace;
+using APixelFormat = aidl::android::hardware::graphics::common::PixelFormat;
+
namespace android {
static const auto kIAllocatorServiceName = IAllocator::descriptor + std::string("/default");
static const auto kIAllocatorMinimumVersion = 2;
+constexpr const char* kStandardMetadataName =
+ "android.hardware.graphics.common.StandardMetadataType";
// TODO(b/72323293, b/72703005): Remove these invalid bits from callers
static constexpr uint64_t kRemovedUsageBits = static_cast<uint64_t>((1 << 10) | (1 << 13));
@@ -284,17 +289,205 @@ bool Gralloc5Mapper::isLoaded() const {
return mMapper != nullptr && mMapper->version >= AIMAPPER_VERSION_5;
}
+static bool isStandardMetadata(AIMapper_MetadataType metadataType) {
+ return strcmp(kStandardMetadataName, metadataType.name) == 0;
+}
+
+struct DumpBufferResult {
+ uint64_t bufferId;
+ std::string name;
+ uint64_t width;
+ uint64_t height;
+ uint64_t layerCount;
+ APixelFormat pixelFormatRequested;
+ uint32_t pixelFormatFourCC;
+ uint64_t pixelFormatModifier;
+ BufferUsage usage;
+ ADataspace dataspace;
+ uint64_t allocationSize;
+ uint64_t protectedContent;
+ ExtendableType compression;
+ ExtendableType interlaced;
+ ExtendableType chromaSiting;
+ std::vector<ui::PlaneLayout> planeLayouts;
+};
+
+#define DECODE_TO(name, output) \
+ case StandardMetadataType::name: \
+ output = StandardMetadata<StandardMetadataType::name>::value ::decode(value, valueSize) \
+ .value(); \
+ break
+
+static void dumpBufferCommon(DumpBufferResult* outResult, AIMapper_MetadataType metadataType,
+ const void* value, size_t valueSize) {
+ if (!isStandardMetadata(metadataType)) {
+ return;
+ }
+ StandardMetadataType type = (StandardMetadataType)metadataType.value;
+ switch (type) {
+ DECODE_TO(BUFFER_ID, outResult->bufferId);
+ DECODE_TO(NAME, outResult->name);
+ DECODE_TO(WIDTH, outResult->width);
+ DECODE_TO(HEIGHT, outResult->height);
+ DECODE_TO(LAYER_COUNT, outResult->layerCount);
+ DECODE_TO(PIXEL_FORMAT_REQUESTED, outResult->pixelFormatRequested);
+ DECODE_TO(PIXEL_FORMAT_FOURCC, outResult->pixelFormatFourCC);
+ DECODE_TO(PIXEL_FORMAT_MODIFIER, outResult->pixelFormatModifier);
+ DECODE_TO(USAGE, outResult->usage);
+ DECODE_TO(DATASPACE, outResult->dataspace);
+ DECODE_TO(ALLOCATION_SIZE, outResult->allocationSize);
+ DECODE_TO(PROTECTED_CONTENT, outResult->protectedContent);
+ DECODE_TO(COMPRESSION, outResult->compression);
+ DECODE_TO(INTERLACED, outResult->interlaced);
+ DECODE_TO(CHROMA_SITING, outResult->chromaSiting);
+ DECODE_TO(PLANE_LAYOUTS, outResult->planeLayouts);
+ default:
+ break;
+ }
+}
+
+#undef DECODE_TO
+
+template <typename EnumT, typename = std::enable_if_t<std::is_enum<EnumT>{}>>
+constexpr std::underlying_type_t<EnumT> to_underlying(EnumT e) noexcept {
+ return static_cast<std::underlying_type_t<EnumT>>(e);
+}
+
+static void writeDumpToStream(const DumpBufferResult& bufferDump, std::ostream& outDump,
+ bool less) {
+ double allocationSizeKiB = static_cast<double>(bufferDump.allocationSize) / 1024;
+
+ outDump << "+ name:" << bufferDump.name << ", id:" << bufferDump.bufferId
+ << ", size:" << std::fixed << allocationSizeKiB << "KiB, w/h:" << bufferDump.width
+ << "x" << bufferDump.height << ", usage: 0x" << std::hex
+ << to_underlying(bufferDump.usage) << std::dec
+ << ", req fmt:" << to_underlying(bufferDump.pixelFormatRequested)
+ << ", fourcc/mod:" << bufferDump.pixelFormatFourCC << "/"
+ << bufferDump.pixelFormatModifier << ", dataspace: 0x" << std::hex
+ << to_underlying(bufferDump.dataspace) << std::dec << ", compressed: ";
+
+ if (less) {
+ bool isCompressed = !gralloc4::isStandardCompression(bufferDump.compression) ||
+ (gralloc4::getStandardCompressionValue(bufferDump.compression) !=
+ ui::Compression::NONE);
+ outDump << std::boolalpha << isCompressed << "\n";
+ } else {
+ outDump << gralloc4::getCompressionName(bufferDump.compression) << "\n";
+ }
+
+ if (!less) {
+ bool firstPlane = true;
+ for (const auto& planeLayout : bufferDump.planeLayouts) {
+ if (firstPlane) {
+ firstPlane = false;
+ outDump << "\tplanes: ";
+ } else {
+ outDump << "\t ";
+ }
+
+ for (size_t i = 0; i < planeLayout.components.size(); i++) {
+ const auto& planeLayoutComponent = planeLayout.components[i];
+ outDump << gralloc4::getPlaneLayoutComponentTypeName(planeLayoutComponent.type);
+ if (i < planeLayout.components.size() - 1) {
+ outDump << "/";
+ } else {
+ outDump << ":\t";
+ }
+ }
+ outDump << " w/h:" << planeLayout.widthInSamples << "x" << planeLayout.heightInSamples
+ << ", stride:" << planeLayout.strideInBytes
+ << " bytes, size:" << planeLayout.totalSizeInBytes;
+ outDump << ", inc:" << planeLayout.sampleIncrementInBits
+ << " bits, subsampling w/h:" << planeLayout.horizontalSubsampling << "x"
+ << planeLayout.verticalSubsampling;
+ outDump << "\n";
+ }
+
+ outDump << "\tlayer cnt: " << bufferDump.layerCount
+ << ", protected content: " << bufferDump.protectedContent
+ << ", interlaced: " << gralloc4::getInterlacedName(bufferDump.interlaced)
+ << ", chroma siting:" << gralloc4::getChromaSitingName(bufferDump.chromaSiting)
+ << "\n";
+ }
+}
+
std::string Gralloc5Mapper::dumpBuffer(buffer_handle_t bufferHandle, bool less) const {
- // TODO(b/261858392): Implement
- (void)bufferHandle;
- (void)less;
- return {};
+ DumpBufferResult bufferInfo;
+ AIMapper_DumpBufferCallback dumpBuffer = [](void* contextPtr,
+ AIMapper_MetadataType metadataType,
+ const void* _Nonnull value, size_t valueSize) {
+ DumpBufferResult* context = reinterpret_cast<DumpBufferResult*>(contextPtr);
+ dumpBufferCommon(context, metadataType, value, valueSize);
+ };
+ AIMapper_Error error = mMapper->v5.dumpBuffer(bufferHandle, dumpBuffer, &bufferInfo);
+ if (error != AIMAPPER_ERROR_NONE) {
+ ALOGE("Error dumping buffer: %d", error);
+ return std::string{};
+ }
+ std::ostringstream stream;
+ stream.precision(2);
+ writeDumpToStream(bufferInfo, stream, less);
+ return stream.str();
}
std::string Gralloc5Mapper::dumpBuffers(bool less) const {
- // TODO(b/261858392): Implement
- (void)less;
- return {};
+ class DumpAllBuffersContext {
+ private:
+ bool mHasPending = false;
+ DumpBufferResult mPending;
+ std::vector<DumpBufferResult> mResults;
+
+ public:
+ DumpAllBuffersContext() { mResults.reserve(10); }
+
+ void commit() {
+ if (mHasPending) {
+ mResults.push_back(mPending);
+ mHasPending = false;
+ }
+ }
+
+ DumpBufferResult* write() {
+ mHasPending = true;
+ return &mPending;
+ }
+
+ const std::vector<DumpBufferResult>& results() {
+ commit();
+ return mResults;
+ }
+ } context;
+
+ AIMapper_BeginDumpBufferCallback beginCallback = [](void* contextPtr) {
+ DumpAllBuffersContext* context = reinterpret_cast<DumpAllBuffersContext*>(contextPtr);
+ context->commit();
+ };
+
+ AIMapper_DumpBufferCallback dumpBuffer = [](void* contextPtr,
+ AIMapper_MetadataType metadataType,
+ const void* _Nonnull value, size_t valueSize) {
+ DumpAllBuffersContext* context = reinterpret_cast<DumpAllBuffersContext*>(contextPtr);
+ dumpBufferCommon(context->write(), metadataType, value, valueSize);
+ };
+
+ AIMapper_Error error = mMapper->v5.dumpAllBuffers(beginCallback, dumpBuffer, &context);
+ if (error != AIMAPPER_ERROR_NONE) {
+ ALOGE("Error dumping buffers: %d", error);
+ return std::string{};
+ }
+ uint64_t totalAllocationSize = 0;
+ std::ostringstream stream;
+ stream.precision(2);
+ stream << "Imported gralloc buffers:\n";
+
+ for (const auto& bufferDump : context.results()) {
+ writeDumpToStream(bufferDump, stream, less);
+ totalAllocationSize += bufferDump.allocationSize;
+ }
+
+ double totalAllocationSizeKiB = static_cast<double>(totalAllocationSize) / 1024;
+ stream << "Total imported by gralloc: " << totalAllocationSizeKiB << "KiB\n";
+ return stream.str();
}
status_t Gralloc5Mapper::importBuffer(const native_handle_t *rawHandle,
@@ -325,14 +518,16 @@ status_t Gralloc5Mapper::validateBufferSize(buffer_handle_t bufferHandle, uint32
}
}
{
- auto value =
- getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_REQUESTED>(mMapper,
- bufferHandle);
- if (static_cast<::aidl::android::hardware::graphics::common::PixelFormat>(format) !=
- value) {
- ALOGW("Format didn't match, expected %d got %s", format,
- value.has_value() ? toString(*value).c_str() : "<null>");
- return BAD_VALUE;
+ auto expected = static_cast<APixelFormat>(format);
+ if (expected != APixelFormat::IMPLEMENTATION_DEFINED) {
+ auto value =
+ getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_REQUESTED>(mMapper,
+ bufferHandle);
+ if (expected != value) {
+ ALOGW("Format didn't match, expected %d got %s", format,
+ value.has_value() ? toString(*value).c_str() : "<null>");
+ return BAD_VALUE;
+ }
}
}
{
diff --git a/libs/ui/GraphicBufferAllocator.cpp b/libs/ui/GraphicBufferAllocator.cpp
index c0abec23e0..eb0bd4ed0a 100644
--- a/libs/ui/GraphicBufferAllocator.cpp
+++ b/libs/ui/GraphicBufferAllocator.cpp
@@ -89,14 +89,14 @@ void GraphicBufferAllocator::dump(std::string& result, bool less) const {
uint64_t total = 0;
result.append("GraphicBufferAllocator buffers:\n");
const size_t count = list.size();
- StringAppendF(&result, "%10s | %11s | %18s | %s | %8s | %10s | %s\n", "Handle", "Size",
+ StringAppendF(&result, "%14s | %11s | %18s | %s | %8s | %10s | %s\n", "Handle", "Size",
"W (Stride) x H", "Layers", "Format", "Usage", "Requestor");
for (size_t i = 0; i < count; i++) {
const alloc_rec_t& rec(list.valueAt(i));
std::string sizeStr = (rec.size)
? base::StringPrintf("%7.2f KiB", static_cast<double>(rec.size) / 1024.0)
: "unknown";
- StringAppendF(&result, "%10p | %11s | %4u (%4u) x %4u | %6u | %8X | 0x%8" PRIx64 " | %s\n",
+ StringAppendF(&result, "%14p | %11s | %4u (%4u) x %4u | %6u | %8X | 0x%8" PRIx64 " | %s\n",
list.keyAt(i), sizeStr.c_str(), rec.width, rec.stride, rec.height,
rec.layerCount, rec.format, rec.usage, rec.requestorName.c_str());
total += rec.size;
diff --git a/libs/ui/include/ui/DisplayMap.h b/libs/ui/include/ui/DisplayMap.h
index 7eacb0a7f0..65d2b8fa31 100644
--- a/libs/ui/include/ui/DisplayMap.h
+++ b/libs/ui/include/ui/DisplayMap.h
@@ -23,13 +23,18 @@ namespace android::ui {
// The static capacities were chosen to exceed a typical number of physical and/or virtual displays.
+constexpr size_t kDisplayCapacity = 5;
template <typename Key, typename Value>
-using DisplayMap = ftl::SmallMap<Key, Value, 5>;
+using DisplayMap = ftl::SmallMap<Key, Value, kDisplayCapacity>;
+constexpr size_t kPhysicalDisplayCapacity = 3;
template <typename Key, typename Value>
-using PhysicalDisplayMap = ftl::SmallMap<Key, Value, 3>;
+using PhysicalDisplayMap = ftl::SmallMap<Key, Value, kPhysicalDisplayCapacity>;
template <typename T>
-using PhysicalDisplayVector = ftl::SmallVector<T, 3>;
+using DisplayVector = ftl::SmallVector<T, kDisplayCapacity>;
+
+template <typename T>
+using PhysicalDisplayVector = ftl::SmallVector<T, kPhysicalDisplayCapacity>;
} // namespace android::ui
diff --git a/libs/ui/include/ui/DisplayMode.h b/libs/ui/include/ui/DisplayMode.h
index 65a8769c98..ddb9bbd4bc 100644
--- a/libs/ui/include/ui/DisplayMode.h
+++ b/libs/ui/include/ui/DisplayMode.h
@@ -37,7 +37,9 @@ struct DisplayMode {
float yDpi = 0;
std::vector<ui::Hdr> supportedHdrTypes;
- float refreshRate = 0;
+ // Some modes have peak refresh rate lower than the panel vsync rate.
+ float peakRefreshRate = 0.f;
+ float vsyncRate = 0.f;
nsecs_t appVsyncOffset = 0;
nsecs_t sfVsyncOffset = 0;
nsecs_t presentationDeadline = 0;
diff --git a/libs/ui/include/ui/GraphicBuffer.h b/libs/ui/include/ui/GraphicBuffer.h
index dbe475b805..f859848b0c 100644
--- a/libs/ui/include/ui/GraphicBuffer.h
+++ b/libs/ui/include/ui/GraphicBuffer.h
@@ -36,6 +36,15 @@
#include <hardware/gralloc.h>
+#if defined(__ANDROID_APEX__) || defined(__ANDROID_VNDK__)
+// TODO: Provide alternatives that aren't broken
+#define AHB_CONVERSION \
+ [[deprecated("WARNING: VNDK casts beteween GraphicBuffer & AHardwareBuffer are UNSAFE and " \
+ "will be removed in the future")]]
+#else
+#define AHB_CONVERSION
+#endif
+
namespace android {
class GraphicBufferMapper;
@@ -80,10 +89,10 @@ public:
static sp<GraphicBuffer> from(ANativeWindowBuffer *);
- static GraphicBuffer* fromAHardwareBuffer(AHardwareBuffer*);
- static GraphicBuffer const* fromAHardwareBuffer(AHardwareBuffer const*);
- AHardwareBuffer* toAHardwareBuffer();
- AHardwareBuffer const* toAHardwareBuffer() const;
+ AHB_CONVERSION static GraphicBuffer* fromAHardwareBuffer(AHardwareBuffer*);
+ AHB_CONVERSION static GraphicBuffer const* fromAHardwareBuffer(AHardwareBuffer const*);
+ AHB_CONVERSION AHardwareBuffer* toAHardwareBuffer();
+ AHB_CONVERSION AHardwareBuffer const* toAHardwareBuffer() const;
// Create a GraphicBuffer to be unflatten'ed into or be reallocated.
GraphicBuffer();
diff --git a/libs/ui/include/ui/ShadowSettings.h b/libs/ui/include/ui/ShadowSettings.h
new file mode 100644
index 0000000000..c0b83b8691
--- /dev/null
+++ b/libs/ui/include/ui/ShadowSettings.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include <math/vec4.h>
+#include "FloatRect.h"
+
+namespace android {
+
+/*
+ * Contains the configuration for the shadows drawn by single layer. Shadow follows
+ * material design guidelines.
+ */
+struct ShadowSettings {
+ // Boundaries of the shadow.
+ FloatRect boundaries = FloatRect();
+
+ // Color to the ambient shadow. The alpha is premultiplied.
+ vec4 ambientColor = vec4();
+
+ // Color to the spot shadow. The alpha is premultiplied. The position of the spot shadow
+ // depends on the light position.
+ vec4 spotColor = vec4();
+
+ // Position of the light source used to cast the spot shadow.
+ vec3 lightPos = vec3();
+
+ // Radius of the spot light source. Smaller radius will have sharper edges,
+ // larger radius will have softer shadows
+ float lightRadius = 0.f;
+
+ // Length of the cast shadow. If length is <= 0.f no shadows will be drawn.
+ float length = 0.f;
+
+ // If true fill in the casting layer is translucent and the shadow needs to fill the bounds.
+ // Otherwise the shadow will only be drawn around the edges of the casting layer.
+ bool casterIsTranslucent = false;
+};
+
+static inline bool operator==(const ShadowSettings& lhs, const ShadowSettings& rhs) {
+ return lhs.boundaries == rhs.boundaries && lhs.ambientColor == rhs.ambientColor &&
+ lhs.spotColor == rhs.spotColor && lhs.lightPos == rhs.lightPos &&
+ lhs.lightRadius == rhs.lightRadius && lhs.length == rhs.length &&
+ lhs.casterIsTranslucent == rhs.casterIsTranslucent;
+}
+
+static inline bool operator!=(const ShadowSettings& lhs, const ShadowSettings& rhs) {
+ return !(operator==(lhs, rhs));
+}
+
+} // namespace android \ No newline at end of file
diff --git a/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp
index ad1d57aaee..f1f403548d 100644
--- a/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp
+++ b/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp
@@ -54,7 +54,7 @@ void UltraHdrDecFuzzer::process() {
std::cout << "input buffer size " << jpegImgR.length << std::endl;
std::cout << "image dimensions " << info.width << " x " << info.width << std::endl;
#endif
- size_t outSize = info.width * info.height * ((of == ULTRAHDR_OUTPUT_SDR) ? 4 : 8);
+ size_t outSize = info.width * info.height * ((of == ULTRAHDR_OUTPUT_HDR_LINEAR) ? 8 : 4);
jpegr_uncompressed_struct decodedJpegR;
auto decodedRaw = std::make_unique<uint8_t[]>(outSize);
decodedJpegR.data = decodedRaw.get();
diff --git a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp
index bbe58e0f2e..2d59e8bb88 100644
--- a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp
+++ b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp
@@ -23,22 +23,12 @@
// User include files
#include "ultrahdr/gainmapmath.h"
+#include "ultrahdr/jpegdecoderhelper.h"
#include "ultrahdr/jpegencoderhelper.h"
#include "utils/Log.h"
using namespace android::ultrahdr;
-// constants
-const int kMinWidth = 8;
-const int kMaxWidth = 7680;
-
-const int kMinHeight = 8;
-const int kMaxHeight = 4320;
-
-const int kScaleFactor = 4;
-
-const int kJpegBlock = 16;
-
// Color gamuts for image data, sync with ultrahdr.h
const int kCgMin = ULTRAHDR_COLORGAMUT_UNSPECIFIED + 1;
const int kCgMax = ULTRAHDR_COLORGAMUT_MAX;
@@ -60,7 +50,7 @@ public:
UltraHdrEncFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
void process();
void fillP010Buffer(uint16_t* data, int width, int height, int stride);
- void fill420Buffer(uint8_t* data, int size);
+ void fill420Buffer(uint8_t* data, int width, int height, int stride);
private:
FuzzedDataProvider mFdp;
@@ -70,11 +60,12 @@ void UltraHdrEncFuzzer::fillP010Buffer(uint16_t* data, int width, int height, in
uint16_t* tmp = data;
std::vector<uint16_t> buffer(16);
for (int i = 0; i < buffer.size(); i++) {
- buffer[i] = mFdp.ConsumeIntegralInRange<int>(0, (1 << 10) - 1);
+ buffer[i] = (mFdp.ConsumeIntegralInRange<int>(0, (1 << 10) - 1)) << 6;
}
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i += buffer.size()) {
- memcpy(data + i, buffer.data(), std::min((int)buffer.size(), (width - i)));
+ memcpy(tmp + i, buffer.data(),
+ std::min((int)buffer.size(), (width - i)) * sizeof(*data));
std::shuffle(buffer.begin(), buffer.end(),
std::default_random_engine(std::random_device{}()));
}
@@ -82,13 +73,18 @@ void UltraHdrEncFuzzer::fillP010Buffer(uint16_t* data, int width, int height, in
}
}
-void UltraHdrEncFuzzer::fill420Buffer(uint8_t* data, int size) {
+void UltraHdrEncFuzzer::fill420Buffer(uint8_t* data, int width, int height, int stride) {
+ uint8_t* tmp = data;
std::vector<uint8_t> buffer(16);
mFdp.ConsumeData(buffer.data(), buffer.size());
- for (int i = 0; i < size; i += buffer.size()) {
- memcpy(data + i, buffer.data(), std::min((int)buffer.size(), (size - i)));
- std::shuffle(buffer.begin(), buffer.end(),
- std::default_random_engine(std::random_device{}()));
+ for (int j = 0; j < height; j++) {
+ for (int i = 0; i < width; i += buffer.size()) {
+ memcpy(tmp + i, buffer.data(),
+ std::min((int)buffer.size(), (width - i)) * sizeof(*data));
+ std::shuffle(buffer.begin(), buffer.end(),
+ std::default_random_engine(std::random_device{}()));
+ }
+ tmp += stride;
}
}
@@ -129,9 +125,10 @@ void UltraHdrEncFuzzer::process() {
int height = mFdp.ConsumeIntegralInRange<int>(kMinHeight, kMaxHeight);
height = (height >> 1) << 1;
- std::unique_ptr<uint16_t[]> bufferY = nullptr;
- std::unique_ptr<uint16_t[]> bufferUV = nullptr;
- std::unique_ptr<uint8_t[]> yuv420ImgRaw = nullptr;
+ std::unique_ptr<uint16_t[]> bufferYHdr = nullptr;
+ std::unique_ptr<uint16_t[]> bufferUVHdr = nullptr;
+ std::unique_ptr<uint8_t[]> bufferYSdr = nullptr;
+ std::unique_ptr<uint8_t[]> bufferUVSdr = nullptr;
std::unique_ptr<uint8_t[]> grayImgRaw = nullptr;
if (muxSwitch != 4) {
// init p010 image
@@ -145,30 +142,27 @@ void UltraHdrEncFuzzer::process() {
int bppP010 = 2;
if (isUVContiguous) {
size_t p010Size = yStride * height * 3 / 2;
- bufferY = std::make_unique<uint16_t[]>(p010Size);
- p010Img.data = bufferY.get();
+ bufferYHdr = std::make_unique<uint16_t[]>(p010Size);
+ p010Img.data = bufferYHdr.get();
p010Img.chroma_data = nullptr;
p010Img.chroma_stride = 0;
- fillP010Buffer(bufferY.get(), width, height, yStride);
- fillP010Buffer(bufferY.get() + yStride * height, width, height / 2, yStride);
+ fillP010Buffer(bufferYHdr.get(), width, height, yStride);
+ fillP010Buffer(bufferYHdr.get() + yStride * height, width, height / 2, yStride);
} else {
int uvStride = mFdp.ConsumeIntegralInRange<int>(width, width + 128);
size_t p010YSize = yStride * height;
- bufferY = std::make_unique<uint16_t[]>(p010YSize);
- p010Img.data = bufferY.get();
- fillP010Buffer(bufferY.get(), width, height, yStride);
+ bufferYHdr = std::make_unique<uint16_t[]>(p010YSize);
+ p010Img.data = bufferYHdr.get();
+ fillP010Buffer(bufferYHdr.get(), width, height, yStride);
size_t p010UVSize = uvStride * p010Img.height / 2;
- bufferUV = std::make_unique<uint16_t[]>(p010UVSize);
- p010Img.chroma_data = bufferUV.get();
+ bufferUVHdr = std::make_unique<uint16_t[]>(p010UVSize);
+ p010Img.chroma_data = bufferUVHdr.get();
p010Img.chroma_stride = uvStride;
- fillP010Buffer(bufferUV.get(), width, height / 2, uvStride);
+ fillP010Buffer(bufferUVHdr.get(), width, height / 2, uvStride);
}
} else {
- int map_width = width / kScaleFactor;
- int map_height = height / kScaleFactor;
- map_width = static_cast<size_t>(floor((map_width + kJpegBlock - 1) / kJpegBlock)) *
- kJpegBlock;
- map_height = ((map_height + 1) >> 1) << 1;
+ size_t map_width = width / kMapDimensionScaleFactor;
+ size_t map_height = height / kMapDimensionScaleFactor;
// init 400 image
grayImg.width = map_width;
grayImg.height = map_height;
@@ -177,7 +171,7 @@ void UltraHdrEncFuzzer::process() {
const size_t graySize = map_width * map_height;
grayImgRaw = std::make_unique<uint8_t[]>(graySize);
grayImg.data = grayImgRaw.get();
- fill420Buffer(grayImgRaw.get(), graySize);
+ fill420Buffer(grayImgRaw.get(), map_width, map_height, map_width);
grayImg.chroma_data = nullptr;
grayImg.luma_stride = 0;
grayImg.chroma_stride = 0;
@@ -185,17 +179,38 @@ void UltraHdrEncFuzzer::process() {
if (muxSwitch > 0) {
// init 420 image
+ bool isUVContiguous = mFdp.ConsumeBool();
+ bool hasYStride = mFdp.ConsumeBool();
+ int yStride = hasYStride ? mFdp.ConsumeIntegralInRange<int>(width, width + 128) : width;
yuv420Img.width = width;
yuv420Img.height = height;
yuv420Img.colorGamut = yuv420Cg;
-
- const size_t yuv420Size = (yuv420Img.width * yuv420Img.height * 3) / 2;
- yuv420ImgRaw = std::make_unique<uint8_t[]>(yuv420Size);
- yuv420Img.data = yuv420ImgRaw.get();
- fill420Buffer(yuv420ImgRaw.get(), yuv420Size);
- yuv420Img.chroma_data = nullptr;
- yuv420Img.luma_stride = 0;
- yuv420Img.chroma_stride = 0;
+ yuv420Img.luma_stride = hasYStride ? yStride : 0;
+ if (isUVContiguous) {
+ size_t yuv420Size = yStride * height * 3 / 2;
+ bufferYSdr = std::make_unique<uint8_t[]>(yuv420Size);
+ yuv420Img.data = bufferYSdr.get();
+ yuv420Img.chroma_data = nullptr;
+ yuv420Img.chroma_stride = 0;
+ fill420Buffer(bufferYSdr.get(), width, height, yStride);
+ fill420Buffer(bufferYSdr.get() + yStride * height, width / 2, height / 2,
+ yStride / 2);
+ fill420Buffer(bufferYSdr.get() + yStride * height * 5 / 4, width / 2, height / 2,
+ yStride / 2);
+ } else {
+ int uvStride = mFdp.ConsumeIntegralInRange<int>(width / 2, width / 2 + 128);
+ size_t yuv420YSize = yStride * height;
+ bufferYSdr = std::make_unique<uint8_t[]>(yuv420YSize);
+ yuv420Img.data = bufferYSdr.get();
+ fill420Buffer(bufferYSdr.get(), width, height, yStride);
+ size_t yuv420UVSize = uvStride * yuv420Img.height / 2 * 2;
+ bufferUVSdr = std::make_unique<uint8_t[]>(yuv420UVSize);
+ yuv420Img.chroma_data = bufferUVSdr.get();
+ yuv420Img.chroma_stride = uvStride;
+ fill420Buffer(bufferUVSdr.get(), width / 2, height / 2, uvStride);
+ fill420Buffer(bufferUVSdr.get() + uvStride * height / 2, width / 2, height / 2,
+ uvStride);
+ }
}
// dest
@@ -212,6 +227,8 @@ void UltraHdrEncFuzzer::process() {
std::cout << "p010 luma stride " << p010Img.luma_stride << std::endl;
std::cout << "p010 chroma stride " << p010Img.chroma_stride << std::endl;
std::cout << "420 color gamut " << yuv420Img.colorGamut << std::endl;
+ std::cout << "420 luma stride " << yuv420Img.luma_stride << std::endl;
+ std::cout << "420 chroma stride " << yuv420Img.chroma_stride << std::endl;
std::cout << "quality factor " << quality << std::endl;
#endif
@@ -226,8 +243,19 @@ void UltraHdrEncFuzzer::process() {
} else {
// compressed img
JpegEncoderHelper encoder;
- if (encoder.compressImage(yuv420Img.data, yuv420Img.width, yuv420Img.height, quality,
- nullptr, 0)) {
+ struct jpegr_uncompressed_struct yuv420ImgCopy = yuv420Img;
+ if (yuv420ImgCopy.luma_stride == 0) yuv420ImgCopy.luma_stride = yuv420Img.width;
+ if (!yuv420ImgCopy.chroma_data) {
+ uint8_t* data = reinterpret_cast<uint8_t*>(yuv420Img.data);
+ yuv420ImgCopy.chroma_data = data + yuv420Img.luma_stride * yuv420Img.height;
+ yuv420ImgCopy.chroma_stride = yuv420Img.luma_stride >> 1;
+ }
+
+ if (encoder.compressImage(reinterpret_cast<uint8_t*>(yuv420ImgCopy.data),
+ reinterpret_cast<uint8_t*>(yuv420ImgCopy.chroma_data),
+ yuv420ImgCopy.width, yuv420ImgCopy.height,
+ yuv420ImgCopy.luma_stride, yuv420ImgCopy.chroma_stride,
+ quality, nullptr, 0)) {
jpegImg.length = encoder.getCompressedImageSize();
jpegImg.maxLength = jpegImg.length;
jpegImg.data = encoder.getCompressedImagePtr();
@@ -242,14 +270,15 @@ void UltraHdrEncFuzzer::process() {
} else if (muxSwitch == 4) { // api 4
jpegImgR.length = 0;
JpegEncoderHelper gainMapEncoder;
- if (gainMapEncoder.compressImage(grayImg.data, grayImg.width, grayImg.height,
- quality, nullptr, 0, true)) {
+ if (gainMapEncoder.compressImage(reinterpret_cast<uint8_t*>(grayImg.data),
+ nullptr, grayImg.width, grayImg.height,
+ grayImg.width, 0, quality, nullptr, 0)) {
jpegGainMap.length = gainMapEncoder.getCompressedImageSize();
jpegGainMap.maxLength = jpegImg.length;
jpegGainMap.data = gainMapEncoder.getCompressedImagePtr();
jpegGainMap.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
ultrahdr_metadata_struct metadata;
- metadata.version = "1.0";
+ metadata.version = kJpegrVersion;
if (tf == ULTRAHDR_TF_HLG) {
metadata.maxContentBoost = kHlgMaxNits / kSdrWhiteNits;
} else if (tf == ULTRAHDR_TF_PQ) {
@@ -274,7 +303,8 @@ void UltraHdrEncFuzzer::process() {
jpegr_info_struct info{0, 0, &iccData, &exifData};
status = jpegHdr.getJPEGRInfo(&jpegImgR, &info);
if (status == android::OK) {
- size_t outSize = info.width * info.height * ((of == ULTRAHDR_OUTPUT_SDR) ? 4 : 8);
+ size_t outSize =
+ info.width * info.height * ((of == ULTRAHDR_OUTPUT_HDR_LINEAR) ? 8 : 4);
jpegr_uncompressed_struct decodedJpegR;
auto decodedRaw = std::make_unique<uint8_t[]>(outSize);
decodedJpegR.data = decodedRaw.get();
diff --git a/libs/ultrahdr/gainmapmath.cpp b/libs/ultrahdr/gainmapmath.cpp
index ee15363b69..ae9c4ca338 100644
--- a/libs/ultrahdr/gainmapmath.cpp
+++ b/libs/ultrahdr/gainmapmath.cpp
@@ -168,7 +168,7 @@ Color srgbInvOetf(Color e_gamma) {
// See IEC 61966-2-1, Equations F.5 and F.6.
float srgbInvOetfLUT(float e_gamma) {
- uint32_t value = static_cast<uint32_t>(e_gamma * kSrgbInvOETFNumEntries);
+ uint32_t value = static_cast<uint32_t>(e_gamma * (kSrgbInvOETFNumEntries - 1) + 0.5);
//TODO() : Remove once conversion modules have appropriate clamping in place
value = CLIP3(value, 0, kSrgbInvOETFNumEntries - 1);
return kSrgbInvOETF[value];
@@ -288,7 +288,7 @@ Color hlgOetf(Color e) {
}
float hlgOetfLUT(float e) {
- uint32_t value = static_cast<uint32_t>(e * kHlgOETFNumEntries);
+ uint32_t value = static_cast<uint32_t>(e * (kHlgOETFNumEntries - 1) + 0.5);
//TODO() : Remove once conversion modules have appropriate clamping in place
value = CLIP3(value, 0, kHlgOETFNumEntries - 1);
@@ -315,7 +315,7 @@ Color hlgInvOetf(Color e_gamma) {
}
float hlgInvOetfLUT(float e_gamma) {
- uint32_t value = static_cast<uint32_t>(e_gamma * kHlgInvOETFNumEntries);
+ uint32_t value = static_cast<uint32_t>(e_gamma * (kHlgInvOETFNumEntries - 1) + 0.5);
//TODO() : Remove once conversion modules have appropriate clamping in place
value = CLIP3(value, 0, kHlgInvOETFNumEntries - 1);
@@ -344,7 +344,7 @@ Color pqOetf(Color e) {
}
float pqOetfLUT(float e) {
- uint32_t value = static_cast<uint32_t>(e * kPqOETFNumEntries);
+ uint32_t value = static_cast<uint32_t>(e * (kPqOETFNumEntries - 1) + 0.5);
//TODO() : Remove once conversion modules have appropriate clamping in place
value = CLIP3(value, 0, kPqOETFNumEntries - 1);
@@ -376,7 +376,7 @@ Color pqInvOetf(Color e_gamma) {
}
float pqInvOetfLUT(float e_gamma) {
- uint32_t value = static_cast<uint32_t>(e_gamma * kPqInvOETFNumEntries);
+ uint32_t value = static_cast<uint32_t>(e_gamma * (kPqInvOETFNumEntries - 1) + 0.5);
//TODO() : Remove once conversion modules have appropriate clamping in place
value = CLIP3(value, 0, kPqInvOETFNumEntries - 1);
@@ -531,29 +531,29 @@ void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma
Color new_uv = (yuv1 + yuv2 + yuv3 + yuv4) / 4.0f;
- size_t pixel_y1_idx = x_chroma * 2 + y_chroma * 2 * image->width;
- size_t pixel_y2_idx = (x_chroma * 2 + 1) + y_chroma * 2 * image->width;
- size_t pixel_y3_idx = x_chroma * 2 + (y_chroma * 2 + 1) * image->width;
- size_t pixel_y4_idx = (x_chroma * 2 + 1) + (y_chroma * 2 + 1) * image->width;
+ size_t pixel_y1_idx = x_chroma * 2 + y_chroma * 2 * image->luma_stride;
+ size_t pixel_y2_idx = (x_chroma * 2 + 1) + y_chroma * 2 * image->luma_stride;
+ size_t pixel_y3_idx = x_chroma * 2 + (y_chroma * 2 + 1) * image->luma_stride;
+ size_t pixel_y4_idx = (x_chroma * 2 + 1) + (y_chroma * 2 + 1) * image->luma_stride;
uint8_t& y1_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y1_idx];
uint8_t& y2_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y2_idx];
uint8_t& y3_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y3_idx];
uint8_t& y4_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y4_idx];
- size_t pixel_count = image->width * image->height;
- size_t pixel_uv_idx = x_chroma + y_chroma * (image->width / 2);
+ size_t pixel_count = image->chroma_stride * image->height / 2;
+ size_t pixel_uv_idx = x_chroma + y_chroma * (image->chroma_stride);
- uint8_t& u_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_count + pixel_uv_idx];
- uint8_t& v_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_count * 5 / 4 + pixel_uv_idx];
+ uint8_t& u_uint = reinterpret_cast<uint8_t*>(image->chroma_data)[pixel_uv_idx];
+ uint8_t& v_uint = reinterpret_cast<uint8_t*>(image->chroma_data)[pixel_count + pixel_uv_idx];
- y1_uint = static_cast<uint8_t>(floor(yuv1.y * 255.0f + 0.5f));
- y2_uint = static_cast<uint8_t>(floor(yuv2.y * 255.0f + 0.5f));
- y3_uint = static_cast<uint8_t>(floor(yuv3.y * 255.0f + 0.5f));
- y4_uint = static_cast<uint8_t>(floor(yuv4.y * 255.0f + 0.5f));
+ y1_uint = static_cast<uint8_t>(CLIP3((yuv1.y * 255.0f + 0.5f), 0, 255));
+ y2_uint = static_cast<uint8_t>(CLIP3((yuv2.y * 255.0f + 0.5f), 0, 255));
+ y3_uint = static_cast<uint8_t>(CLIP3((yuv3.y * 255.0f + 0.5f), 0, 255));
+ y4_uint = static_cast<uint8_t>(CLIP3((yuv4.y * 255.0f + 0.5f), 0, 255));
- u_uint = static_cast<uint8_t>(floor(new_uv.u * 255.0f + 128.0f + 0.5f));
- v_uint = static_cast<uint8_t>(floor(new_uv.v * 255.0f + 128.0f + 0.5f));
+ u_uint = static_cast<uint8_t>(CLIP3((new_uv.u * 255.0f + 128.0f + 0.5f), 0, 255));
+ v_uint = static_cast<uint8_t>(CLIP3((new_uv.v * 255.0f + 128.0f + 0.5f), 0, 255));
}
////////////////////////////////////////////////////////////////////////////////
@@ -598,14 +598,18 @@ Color applyGainLUT(Color e, float gain, GainLUT& gainLUT) {
}
Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
- size_t pixel_count = image->width * image->height;
+ uint8_t* luma_data = reinterpret_cast<uint8_t*>(image->data);
+ size_t luma_stride = image->luma_stride;
+ uint8_t* chroma_data = reinterpret_cast<uint8_t*>(image->chroma_data);
+ size_t chroma_stride = image->chroma_stride;
- size_t pixel_y_idx = x + y * image->width;
- size_t pixel_uv_idx = x / 2 + (y / 2) * (image->width / 2);
+ size_t offset_cr = chroma_stride * (image->height / 2);
+ size_t pixel_y_idx = x + y * luma_stride;
+ size_t pixel_chroma_idx = x / 2 + (y / 2) * chroma_stride;
- uint8_t y_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y_idx];
- uint8_t u_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_count + pixel_uv_idx];
- uint8_t v_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_count * 5 / 4 + pixel_uv_idx];
+ uint8_t y_uint = luma_data[pixel_y_idx];
+ uint8_t u_uint = chroma_data[pixel_chroma_idx];
+ uint8_t v_uint = chroma_data[offset_cr + pixel_chroma_idx];
// 128 bias for UV given we are using jpeglib; see:
// https://github.com/kornelski/libjpeg/blob/master/structure.doc
@@ -615,20 +619,10 @@ Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
}
Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
- size_t luma_stride = image->luma_stride;
- size_t chroma_stride = image->chroma_stride;
uint16_t* luma_data = reinterpret_cast<uint16_t*>(image->data);
+ size_t luma_stride = image->luma_stride == 0 ? image->width : image->luma_stride;
uint16_t* chroma_data = reinterpret_cast<uint16_t*>(image->chroma_data);
-
- if (luma_stride == 0) {
- luma_stride = image->width;
- }
- if (chroma_stride == 0) {
- chroma_stride = luma_stride;
- }
- if (chroma_data == nullptr) {
- chroma_data = &reinterpret_cast<uint16_t*>(image->data)[luma_stride * image->height];
- }
+ size_t chroma_stride = image->chroma_stride;
size_t pixel_y_idx = y * luma_stride + x;
size_t pixel_u_idx = (y >> 1) * chroma_stride + (x & ~0x1);
diff --git a/libs/ultrahdr/icc.cpp b/libs/ultrahdr/icc.cpp
index 1ab3c7c793..e41b645cce 100644
--- a/libs/ultrahdr/icc.cpp
+++ b/libs/ultrahdr/icc.cpp
@@ -19,7 +19,6 @@
#endif
#include <ultrahdr/icc.h>
-#include <ultrahdr/gainmapmath.h>
#include <vector>
#include <utils/Log.h>
@@ -218,8 +217,8 @@ sp<DataStruct> IccHelper::write_trc_tag(const int table_entries, const void* tab
total_length = (((total_length + 2) >> 2) << 2); // 4 aligned
sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
dataStruct->write32(Endian_SwapBE32(kTAG_CurveType)); // Type
- dataStruct->write32(0); // Reserved
- dataStruct->write32(Endian_SwapBE32(table_entries)); // Value count
+ dataStruct->write32(0); // Reserved
+ dataStruct->write32(Endian_SwapBE32(table_entries)); // Value count
for (size_t i = 0; i < table_entries; ++i) {
uint16_t value = reinterpret_cast<const uint16_t*>(table_16)[i];
dataStruct->write16(value);
@@ -227,14 +226,30 @@ sp<DataStruct> IccHelper::write_trc_tag(const int table_entries, const void* tab
return dataStruct;
}
-sp<DataStruct> IccHelper::write_trc_tag_for_linear() {
- int total_length = 16;
- sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
- dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType)); // Type
- dataStruct->write32(0); // Reserved
- dataStruct->write32(Endian_SwapBE16(kExponential_ParaCurveType));
- dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(1.0)));
+sp<DataStruct> IccHelper::write_trc_tag(const TransferFunction& fn) {
+ if (fn.a == 1.f && fn.b == 0.f && fn.c == 0.f
+ && fn.d == 0.f && fn.e == 0.f && fn.f == 0.f) {
+ int total_length = 16;
+ sp<DataStruct> dataStruct = new DataStruct(total_length);
+ dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType)); // Type
+ dataStruct->write32(0); // Reserved
+ dataStruct->write32(Endian_SwapBE16(kExponential_ParaCurveType));
+ dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.g)));
+ return dataStruct;
+ }
+ int total_length = 40;
+ sp<DataStruct> dataStruct = new DataStruct(total_length);
+ dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType)); // Type
+ dataStruct->write32(0); // Reserved
+ dataStruct->write32(Endian_SwapBE16(kGABCDEF_ParaCurveType));
+ dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.g)));
+ dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.a)));
+ dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.b)));
+ dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.c)));
+ dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.d)));
+ dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.e)));
+ dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.f)));
return dataStruct;
}
@@ -349,7 +364,7 @@ sp<DataStruct> IccHelper::write_mAB_or_mBA_tag(uint32_t type,
// The "B" curve is required.
for (size_t i = 0; i < kNumChannels; ++i) {
- b_curves_data[i] = write_trc_tag_for_linear();
+ b_curves_data[i] = write_trc_tag(kLinear_TransFun);
}
// The "A" curve and CLUT are optional.
@@ -362,7 +377,7 @@ sp<DataStruct> IccHelper::write_mAB_or_mBA_tag(uint32_t type,
a_curves_offset = clut_offset + clut->getLength();
for (size_t i = 0; i < kNumChannels; ++i) {
- a_curves_data[i] = write_trc_tag_for_linear();
+ a_curves_data[i] = write_trc_tag(kLinear_TransFun);
}
}
@@ -460,9 +475,9 @@ sp<DataStruct> IccHelper::writeIccProfile(ultrahdr_transfer_function tf,
tags.emplace_back(kTAG_bTRC,
write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data())));
} else {
- tags.emplace_back(kTAG_rTRC, write_trc_tag_for_linear());
- tags.emplace_back(kTAG_gTRC, write_trc_tag_for_linear());
- tags.emplace_back(kTAG_bTRC, write_trc_tag_for_linear());
+ tags.emplace_back(kTAG_rTRC, write_trc_tag(kSRGB_TransFun));
+ tags.emplace_back(kTAG_gTRC, write_trc_tag(kSRGB_TransFun));
+ tags.emplace_back(kTAG_bTRC, write_trc_tag(kSRGB_TransFun));
}
}
diff --git a/libs/ultrahdr/include/ultrahdr/gainmapmath.h b/libs/ultrahdr/include/ultrahdr/gainmapmath.h
index edf152d8ed..9f1238f718 100644
--- a/libs/ultrahdr/include/ultrahdr/gainmapmath.h
+++ b/libs/ultrahdr/include/ultrahdr/gainmapmath.h
@@ -51,6 +51,23 @@ struct Color {
typedef Color (*ColorTransformFn)(Color);
typedef float (*ColorCalculationFn)(Color);
+// A transfer function mapping encoded values to linear values,
+// represented by this 7-parameter piecewise function:
+//
+// linear = sign(encoded) * (c*|encoded| + f) , 0 <= |encoded| < d
+// = sign(encoded) * ((a*|encoded| + b)^g + e), d <= |encoded|
+//
+// (A simple gamma transfer function sets g to gamma and a to 1.)
+typedef struct TransferFunction {
+ float g, a,b,c,d,e,f;
+} TransferFunction;
+
+static constexpr TransferFunction kSRGB_TransFun =
+ { 2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0.0f, 0.0f };
+
+static constexpr TransferFunction kLinear_TransFun =
+ { 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
+
inline Color operator+=(Color& lhs, const Color& rhs) {
lhs.r += rhs.r;
lhs.g += rhs.g;
@@ -155,7 +172,7 @@ struct GainLUT {
}
float getGainFactor(float gain) {
- uint32_t idx = static_cast<uint32_t>(gain * (kGainFactorNumEntries - 1));
+ uint32_t idx = static_cast<uint32_t>(gain * (kGainFactorNumEntries - 1) + 0.5);
//TODO() : Remove once conversion modules have appropriate clamping in place
idx = CLIP3(idx, 0, kGainFactorNumEntries - 1);
return mGainTable[idx];
@@ -312,7 +329,7 @@ Color hlgOetf(Color e);
float hlgOetfLUT(float e);
Color hlgOetfLUT(Color e);
-constexpr size_t kHlgOETFPrecision = 10;
+constexpr size_t kHlgOETFPrecision = 16;
constexpr size_t kHlgOETFNumEntries = 1 << kHlgOETFPrecision;
/*
@@ -325,7 +342,7 @@ Color hlgInvOetf(Color e_gamma);
float hlgInvOetfLUT(float e_gamma);
Color hlgInvOetfLUT(Color e_gamma);
-constexpr size_t kHlgInvOETFPrecision = 10;
+constexpr size_t kHlgInvOETFPrecision = 12;
constexpr size_t kHlgInvOETFNumEntries = 1 << kHlgInvOETFPrecision;
/*
@@ -338,7 +355,7 @@ Color pqOetf(Color e);
float pqOetfLUT(float e);
Color pqOetfLUT(Color e);
-constexpr size_t kPqOETFPrecision = 10;
+constexpr size_t kPqOETFPrecision = 16;
constexpr size_t kPqOETFNumEntries = 1 << kPqOETFPrecision;
/*
@@ -351,7 +368,7 @@ Color pqInvOetf(Color e_gamma);
float pqInvOetfLUT(float e_gamma);
Color pqInvOetfLUT(Color e_gamma);
-constexpr size_t kPqInvOETFPrecision = 10;
+constexpr size_t kPqInvOETFPrecision = 12;
constexpr size_t kPqInvOETFNumEntries = 1 << kPqInvOETFPrecision;
diff --git a/libs/ultrahdr/include/ultrahdr/icc.h b/libs/ultrahdr/include/ultrahdr/icc.h
index 7f047f8f5b..971b267fe4 100644
--- a/libs/ultrahdr/include/ultrahdr/icc.h
+++ b/libs/ultrahdr/include/ultrahdr/icc.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_ULTRAHDR_ICC_H
#define ANDROID_ULTRAHDR_ICC_H
+#include <ultrahdr/gainmapmath.h>
#include <ultrahdr/jpegr.h>
#include <ultrahdr/jpegrutils.h>
#include <utils/RefBase.h>
@@ -222,7 +223,7 @@ private:
const ultrahdr_color_gamut gamut);
static sp<DataStruct> write_xyz_tag(float x, float y, float z);
static sp<DataStruct> write_trc_tag(const int table_entries, const void* table_16);
- static sp<DataStruct> write_trc_tag_for_linear();
+ static sp<DataStruct> write_trc_tag(const TransferFunction& fn);
static float compute_tone_map_gain(const ultrahdr_transfer_function tf, float L);
static sp<DataStruct> write_cicp_tag(uint32_t color_primaries,
uint32_t transfer_characteristics);
diff --git a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
index 8b5499a2c0..b86ce5f450 100644
--- a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
+++ b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
@@ -26,6 +26,8 @@ extern "C" {
#include <utils/Errors.h>
#include <vector>
+// constraint on max width and max height is only due to device alloc constraints
+// Can tune these values basing on the target device
static const int kMaxWidth = 8192;
static const int kMaxHeight = 8192;
@@ -74,15 +76,27 @@ public:
*/
size_t getXMPSize();
/*
+ * Extracts EXIF package and updates the EXIF position / length without decoding the image.
+ */
+ bool extractEXIF(const void* image, int length);
+ /*
* Returns the EXIF data from the image.
+ * This method must be called after extractEXIF() or decompressImage().
*/
void* getEXIFPtr();
/*
* Returns the decompressed EXIF buffer size. This method must be called only after
- * calling decompressImage() or getCompressedImageParameters().
+ * calling decompressImage(), extractEXIF() or getCompressedImageParameters().
*/
size_t getEXIFSize();
/*
+ * Returns the position offset of EXIF package
+ * (4 bypes offset to FF sign, the byte after FF E1 XX XX <this byte>),
+ * or -1 if no EXIF exists.
+ * This method must be called after extractEXIF() or decompressImage().
+ */
+ int getEXIFPos() { return mExifPos; }
+ /*
* Returns the ICC data from the image.
*/
void* getICCPtr();
@@ -94,9 +108,8 @@ public:
/*
* Decompresses metadata of the image. All vectors are owned by the caller.
*/
- bool getCompressedImageParameters(const void* image, int length,
- size_t* pWidth, size_t* pHeight,
- std::vector<uint8_t>* iccData,
+ bool getCompressedImageParameters(const void* image, int length, size_t* pWidth,
+ size_t* pHeight, std::vector<uint8_t>* iccData,
std::vector<uint8_t>* exifData);
private:
@@ -121,6 +134,9 @@ private:
// Resolution of the decompressed image.
size_t mWidth;
size_t mHeight;
+
+ // Position of EXIF package, default value is -1 which means no EXIF package appears.
+ ssize_t mExifPos = -1;
};
} /* namespace android::ultrahdr */
diff --git a/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h
index 2c6778e299..9d06415cb3 100644
--- a/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h
+++ b/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h
@@ -19,6 +19,7 @@
// We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
#include <cstdio>
+#include <vector>
extern "C" {
#include <jerror.h>
@@ -26,10 +27,11 @@ extern "C" {
}
#include <utils/Errors.h>
-#include <vector>
namespace android::ultrahdr {
+#define ALIGNM(x, m) ((((x) + ((m)-1)) / (m)) * (m))
+
/*
* Encapsulates a converter from raw image (YUV420planer or grey-scale) to JPEG format.
* This class is not thread-safe.
@@ -46,8 +48,9 @@ public:
* ICC segment which will be added to the compressed image.
* Returns false if errors occur during compression.
*/
- bool compressImage(const void* image, int width, int height, int quality,
- const void* iccBuffer, unsigned int iccSize, bool isSingleChannel = false);
+ bool compressImage(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, int height,
+ int lumaStride, int chromaStride, int quality, const void* iccBuffer,
+ unsigned int iccSize);
/*
* Returns the compressed JPEG buffer pointer. This method must be called only after calling
@@ -66,6 +69,7 @@ public:
* We must pass at least 16 scanlines according to libjpeg documentation.
*/
static const int kCompressBatchSize = 16;
+
private:
// initDestination(), emptyOutputBuffer() and emptyOutputBuffer() are callback functions to be
// passed into jpeg library.
@@ -75,15 +79,16 @@ private:
static void outputErrorMessage(j_common_ptr cinfo);
// Returns false if errors occur.
- bool encode(const void* inYuv, int width, int height, int jpegQuality,
- const void* iccBuffer, unsigned int iccSize, bool isSingleChannel);
+ bool encode(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, int height,
+ int lumaStride, int chromaStride, int quality, const void* iccBuffer,
+ unsigned int iccSize);
void setJpegDestination(jpeg_compress_struct* cinfo);
void setJpegCompressStruct(int width, int height, int quality, jpeg_compress_struct* cinfo,
bool isSingleChannel);
// Returns false if errors occur.
- bool compress(jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel);
- bool compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv);
- bool compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image);
+ bool compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, const uint8_t* uvBuffer,
+ int lumaStride, int chromaStride);
+ bool compressY(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, int lumaStride);
// The block size for encoded jpeg image buffer.
static const int kBlockSize = 16384;
diff --git a/libs/ultrahdr/include/ultrahdr/jpegr.h b/libs/ultrahdr/include/ultrahdr/jpegr.h
index f80496a758..114c81d818 100644
--- a/libs/ultrahdr/include/ultrahdr/jpegr.h
+++ b/libs/ultrahdr/include/ultrahdr/jpegr.h
@@ -17,9 +17,13 @@
#ifndef ANDROID_ULTRAHDR_JPEGR_H
#define ANDROID_ULTRAHDR_JPEGR_H
-#include "jpegencoderhelper.h"
-#include "jpegrerrorcode.h"
-#include "ultrahdr.h"
+#include <cstdint>
+#include <vector>
+
+#include "ultrahdr/jpegdecoderhelper.h"
+#include "ultrahdr/jpegencoderhelper.h"
+#include "ultrahdr/jpegrerrorcode.h"
+#include "ultrahdr/ultrahdr.h"
#ifndef FLT_MAX
#define FLT_MAX 0x1.fffffep127f
@@ -27,6 +31,27 @@
namespace android::ultrahdr {
+// The current JPEGR version that we encode to
+static const char* const kJpegrVersion = "1.0";
+
+// Map is quarter res / sixteenth size
+static const size_t kMapDimensionScaleFactor = 4;
+
+// Gain Map width is (image_width / kMapDimensionScaleFactor). If we were to
+// compress 420 GainMap in jpeg, then we need at least 2 samples. For Grayscale
+// 1 sample is sufficient. We are using 2 here anyways
+static const int kMinWidth = 2 * kMapDimensionScaleFactor;
+static const int kMinHeight = 2 * kMapDimensionScaleFactor;
+
+// Minimum Codec Unit(MCU) for 420 sub-sampling is decided by JPEG encoder by parameter
+// JpegEncoderHelper::kCompressBatchSize.
+// The width and height of image under compression is expected to be a multiple of MCU size.
+// If this criteria is not satisfied, padding is done.
+static const size_t kJpegBlock = JpegEncoderHelper::kCompressBatchSize;
+
+/*
+ * Holds information of jpegr image
+ */
struct jpegr_info_struct {
size_t width;
size_t height;
@@ -49,16 +74,19 @@ struct jpegr_uncompressed_struct {
// Values below are optional
// Pointer to chroma data, if it's NULL, chroma plane is considered to be immediately
- // following after the luma plane.
- // Note: currently this feature is only supported for P010 image (HDR input).
+ // after the luma plane.
void* chroma_data = nullptr;
- // Strides of Y plane in number of pixels, using 0 to present uninitialized, must be
- // larger than or equal to luma width.
- // Note: currently this feature is only supported for P010 image (HDR input).
+ // Stride of Y plane in number of pixels. 0 indicates the member is uninitialized. If
+ // non-zero this value must be larger than or equal to luma width. If stride is
+ // uninitialized then it is assumed to be equal to luma width.
int luma_stride = 0;
- // Strides of UV plane in number of pixels, using 0 to present uninitialized, must be
- // larger than or equal to chroma width.
- // Note: currently this feature is only supported for P010 image (HDR input).
+ // Stride of UV plane in number of pixels.
+ // 1. If this handle points to P010 image then this value must be larger than
+ // or equal to luma width.
+ // 2. If this handle points to 420 image then this value must be larger than
+ // or equal to (luma width / 2).
+ // NOTE: if chroma_data is nullptr, chroma_stride is irrelevant. Just as the way,
+ // chroma_data is derived from luma ptr, chroma stride is derived from luma stride.
int chroma_stride = 0;
};
@@ -102,10 +130,10 @@ public:
* Tonemap the HDR input to a SDR image, generate gain map from the HDR and SDR images,
* compress SDR YUV to 8-bit JPEG and append the gain map to the end of the compressed
* JPEG.
- * @param uncompressed_p010_image uncompressed HDR image in P010 color format
+ * @param p010_image_ptr uncompressed HDR image in P010 color format
* @param hdr_tf transfer function of the HDR image
* @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
- * represents the maximum available size of the desitination buffer, and it must be
+ * represents the maximum available size of the destination buffer, and it must be
* set before calling this method. If the encoded JPEGR size exceeds
* {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
* @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
@@ -113,11 +141,8 @@ public:
* @param exif pointer to the exif metadata.
* @return NO_ERROR if encoding succeeds, error code if error occurs.
*/
- status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
- ultrahdr_transfer_function hdr_tf,
- jr_compressed_ptr dest,
- int quality,
- jr_exif_ptr exif);
+ status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
+ jr_compressed_ptr dest, int quality, jr_exif_ptr exif);
/*
* Encode API-1
@@ -126,8 +151,8 @@ public:
* Generate gain map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append
* the gain map to the end of the compressed JPEG. HDR and SDR inputs must be the same
* resolution. SDR input is assumed to use the sRGB transfer function.
- * @param uncompressed_p010_image uncompressed HDR image in P010 color format
- * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+ * @param p010_image_ptr uncompressed HDR image in P010 color format
+ * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
* @param hdr_tf transfer function of the HDR image
* @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
* represents the maximum available size of the desitination buffer, and it must be
@@ -138,11 +163,8 @@ public:
* @param exif pointer to the exif metadata.
* @return NO_ERROR if encoding succeeds, error code if error occurs.
*/
- status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
- jr_uncompressed_ptr uncompressed_yuv_420_image,
- ultrahdr_transfer_function hdr_tf,
- jr_compressed_ptr dest,
- int quality,
+ status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_uncompressed_ptr yuv420_image_ptr,
+ ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest, int quality,
jr_exif_ptr exif);
/*
@@ -155,11 +177,11 @@ public:
* compressed JPEG. Adds an ICC profile if one isn't present in the input JPEG image. HDR and
* SDR inputs must be the same resolution and color space. SDR image is assumed to use the sRGB
* transfer function.
- * @param uncompressed_p010_image uncompressed HDR image in P010 color format
- * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
- * Note: the SDR image must be the decoded version of the JPEG
- * input
- * @param compressed_jpeg_image compressed 8-bit JPEG image
+ * @param p010_image_ptr uncompressed HDR image in P010 color format
+ * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
+ * @param yuv420jpg_image_ptr SDR image compressed in jpeg format
+ * Note: the compressed SDR image must be the compressed
+ * yuv420_image_ptr image in JPEG format.
* @param hdr_tf transfer function of the HDR image
* @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
* represents the maximum available size of the desitination buffer, and it must be
@@ -167,10 +189,8 @@ public:
* {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
* @return NO_ERROR if encoding succeeds, error code if error occurs.
*/
- status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
- jr_uncompressed_ptr uncompressed_yuv_420_image,
- jr_compressed_ptr compressed_jpeg_image,
- ultrahdr_transfer_function hdr_tf,
+ status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_uncompressed_ptr yuv420_image_ptr,
+ jr_compressed_ptr yuv420jpg_image_ptr, ultrahdr_transfer_function hdr_tf,
jr_compressed_ptr dest);
/*
@@ -183,8 +203,8 @@ public:
* and the decoded SDR result, append the gain map to the end of the compressed JPEG. Adds an
* ICC profile if one isn't present in the input JPEG image. HDR and SDR inputs must be the same
* resolution. JPEG image is assumed to use the sRGB transfer function.
- * @param uncompressed_p010_image uncompressed HDR image in P010 color format
- * @param compressed_jpeg_image compressed 8-bit JPEG image
+ * @param p010_image_ptr uncompressed HDR image in P010 color format
+ * @param yuv420jpg_image_ptr SDR image compressed in jpeg format
* @param hdr_tf transfer function of the HDR image
* @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
* represents the maximum available size of the desitination buffer, and it must be
@@ -192,10 +212,8 @@ public:
* {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
* @return NO_ERROR if encoding succeeds, error code if error occurs.
*/
- status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
- jr_compressed_ptr compressed_jpeg_image,
- ultrahdr_transfer_function hdr_tf,
- jr_compressed_ptr dest);
+ status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_compressed_ptr yuv420jpg_image_ptr,
+ ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest);
/*
* Encode API-4
@@ -203,8 +221,8 @@ public:
*
* Assemble the primary JPEG image, the gain map and the metadata to JPEG/R format. Adds an ICC
* profile if one isn't present in the input JPEG image.
- * @param compressed_jpeg_image compressed 8-bit JPEG image
- * @param compressed_gainmap compressed 8-bit JPEG single channel image
+ * @param yuv420jpg_image_ptr SDR image compressed in jpeg format
+ * @param gainmapjpg_image_ptr gain map image compressed in jpeg format
* @param metadata metadata to be written in XMP of the primary jpeg
* @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
* represents the maximum available size of the desitination buffer, and it must be
@@ -212,9 +230,8 @@ public:
* {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
* @return NO_ERROR if encoding succeeds, error code if error occurs.
*/
- status_t encodeJPEGR(jr_compressed_ptr compressed_jpeg_image,
- jr_compressed_ptr compressed_gainmap,
- ultrahdr_metadata_ptr metadata,
+ status_t encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr,
+ jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata,
jr_compressed_ptr dest);
/*
@@ -227,8 +244,7 @@ public:
*
* This method only supports single gain map metadata values for fields that allow multi-channel
* metadata values.
- *
- * @param compressed_jpegr_image compressed JPEGR image.
+ * @param jpegr_image_ptr compressed JPEGR image.
* @param dest destination of the uncompressed JPEGR image.
* @param max_display_boost (optional) the maximum available boost supported by a display,
* the value must be greater than or equal to 1.0.
@@ -248,57 +264,55 @@ public:
----------------------------------------------------------------------
| JPEGR_OUTPUT_HDR_HLG | RGBA_1010102 HLG |
----------------------------------------------------------------------
- * @param gain_map destination of the decoded gain map. The default value is NULL where
- the decoder will do nothing about it. If configured not NULL the decoder
- will write the decoded gain_map data into this structure. The format
- is defined in {@code jpegr_uncompressed_struct}.
+ * @param gainmap_image_ptr destination of the decoded gain map. The default value is NULL
+ where the decoder will do nothing about it. If configured not NULL
+ the decoder will write the decoded gain_map data into this
+ structure. The format is defined in
+ {@code jpegr_uncompressed_struct}.
* @param metadata destination of the decoded metadata. The default value is NULL where the
decoder will do nothing about it. If configured not NULL the decoder will
write metadata into this structure. the format of metadata is defined in
{@code ultrahdr_metadata_struct}.
* @return NO_ERROR if decoding succeeds, error code if error occurs.
*/
- status_t decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
- jr_uncompressed_ptr dest,
- float max_display_boost = FLT_MAX,
- jr_exif_ptr exif = nullptr,
+ status_t decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest,
+ float max_display_boost = FLT_MAX, jr_exif_ptr exif = nullptr,
ultrahdr_output_format output_format = ULTRAHDR_OUTPUT_HDR_LINEAR,
- jr_uncompressed_ptr gain_map = nullptr,
+ jr_uncompressed_ptr gainmap_image_ptr = nullptr,
ultrahdr_metadata_ptr metadata = nullptr);
/*
- * Gets Info from JPEGR file without decoding it.
- *
- * This method only supports single gain map metadata values for fields that allow multi-channel
- * metadata values.
- *
- * The output is filled jpegr_info structure
- * @param compressed_jpegr_image compressed JPEGR image
- * @param jpegr_info pointer to output JPEGR info. Members of jpegr_info
- * are owned by the caller
- * @return NO_ERROR if JPEGR parsing succeeds, error code otherwise
- */
- status_t getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
- jr_info_ptr jpegr_info);
+ * Gets Info from JPEGR file without decoding it.
+ *
+ * This method only supports single gain map metadata values for fields that allow multi-channel
+ * metadata values.
+ *
+ * The output is filled jpegr_info structure
+ * @param jpegr_image_ptr compressed JPEGR image
+ * @param jpeg_image_info_ptr pointer to jpegr info struct. Members of jpegr_info
+ * are owned by the caller
+ * @return NO_ERROR if JPEGR parsing succeeds, error code otherwise
+ */
+ status_t getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr);
+
protected:
/*
* This method is called in the encoding pipeline. It will take the uncompressed 8-bit and
* 10-bit yuv images as input, and calculate the uncompressed gain map. The input images
* must be the same resolution. The SDR input is assumed to use the sRGB transfer function.
*
- * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
- * @param uncompressed_p010_image uncompressed HDR image in P010 color format
+ * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
+ * @param p010_image_ptr uncompressed HDR image in P010 color format
* @param hdr_tf transfer function of the HDR image
- * @param dest gain map; caller responsible for memory of data
- * @param metadata max_content_boost is filled in
+ * @param metadata everything but "version" is filled in this struct
+ * @param dest location at which gain map image is stored (caller responsible for memory
+ of data).
* @param sdr_is_601 if true, then use BT.601 decoding of YUV regardless of SDR image gamut
* @return NO_ERROR if calculation succeeds, error code if error occurs.
*/
- status_t generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
- jr_uncompressed_ptr uncompressed_p010_image,
- ultrahdr_transfer_function hdr_tf,
- ultrahdr_metadata_ptr metadata,
- jr_uncompressed_ptr dest,
+ status_t generateGainMap(jr_uncompressed_ptr yuv420_image_ptr,
+ jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
+ ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest,
bool sdr_is_601 = false);
/*
@@ -309,8 +323,8 @@ protected:
* The SDR image is assumed to use the sRGB transfer function. The SDR image is also assumed to
* be a decoded JPEG for the purpose of YUV interpration.
*
- * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
- * @param uncompressed_gain_map uncompressed gain map
+ * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
+ * @param gainmap_image_ptr pointer to uncompressed gain map image struct.
* @param metadata JPEG/R metadata extracted from XMP.
* @param output_format flag for setting output color format. if set to
* {@code JPEGR_OUTPUT_SDR}, decoder will only decode the primary image
@@ -319,70 +333,67 @@ protected:
* @param dest reconstructed HDR image
* @return NO_ERROR if calculation succeeds, error code if error occurs.
*/
- status_t applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
- jr_uncompressed_ptr uncompressed_gain_map,
- ultrahdr_metadata_ptr metadata,
- ultrahdr_output_format output_format,
- float max_display_boost,
+ status_t applyGainMap(jr_uncompressed_ptr yuv420_image_ptr,
+ jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata,
+ ultrahdr_output_format output_format, float max_display_boost,
jr_uncompressed_ptr dest);
private:
/*
* This method is called in the encoding pipeline. It will encode the gain map.
*
- * @param uncompressed_gain_map uncompressed gain map
- * @param resource to compress gain map
+ * @param gainmap_image_ptr pointer to uncompressed gain map image struct
+ * @param jpeg_enc_obj_ptr helper resource to compress gain map
* @return NO_ERROR if encoding succeeds, error code if error occurs.
*/
- status_t compressGainMap(jr_uncompressed_ptr uncompressed_gain_map,
- JpegEncoderHelper* jpeg_encoder);
+ status_t compressGainMap(jr_uncompressed_ptr gainmap_image_ptr,
+ JpegEncoderHelper* jpeg_enc_obj_ptr);
/*
- * This methoud is called to separate primary image and gain map image from JPEGR
+ * This method is called to separate primary image and gain map image from JPEGR
*
- * @param compressed_jpegr_image compressed JPEGR image
- * @param primary_image destination of primary image
- * @param gain_map destination of compressed gain map
+ * @param jpegr_image_ptr pointer to compressed JPEGR image.
+ * @param primary_jpg_image_ptr destination of primary image
+ * @param gainmap_jpg_image_ptr destination of compressed gain map image
* @return NO_ERROR if calculation succeeds, error code if error occurs.
- */
- status_t extractPrimaryImageAndGainMap(jr_compressed_ptr compressed_jpegr_image,
- jr_compressed_ptr primary_image,
- jr_compressed_ptr gain_map);
+ */
+ status_t extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
+ jr_compressed_ptr primary_jpg_image_ptr,
+ jr_compressed_ptr gainmap_jpg_image_ptr);
/*
* This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image,
* the compressed gain map and optionally the exif package as inputs, and generate the XMP
* metadata, and finally append everything in the order of:
* SOI, APP2(EXIF) (if EXIF is from outside), APP2(XMP), primary image, gain map
- * Note that EXIF package is only available for encoding API-0 and API-1. For encoding API-2 and
- * API-3 this parameter is null, but the primary image in JPEG/R may still have EXIF as long as
- * the input JPEG has EXIF.
*
- * @param compressed_jpeg_image compressed 8-bit JPEG image
- * @param compress_gain_map compressed recover map
- * @param (nullable) exif EXIF package
- * @param (nullable) icc ICC package
+ * Note that in the final JPEG/R output, EXIF package will appear if ONLY ONE of the following
+ * conditions is fulfilled:
+ * (1) EXIF package is available from outside input. I.e. pExif != nullptr.
+ * (2) Input JPEG has EXIF.
+ * If both conditions are fulfilled, this method will return ERROR_JPEGR_INVALID_INPUT_TYPE
+ *
+ * @param primary_jpg_image_ptr destination of primary image
+ * @param gainmap_jpg_image_ptr destination of compressed gain map image
+ * @param (nullable) pExif EXIF package
+ * @param (nullable) pIcc ICC package
* @param icc_size length in bytes of ICC package
* @param metadata JPEG/R metadata to encode in XMP of the jpeg
* @param dest compressed JPEGR image
* @return NO_ERROR if calculation succeeds, error code if error occurs.
*/
- status_t appendGainMap(jr_compressed_ptr compressed_jpeg_image,
- jr_compressed_ptr compressed_gain_map,
- jr_exif_ptr exif,
- void* icc, size_t icc_size,
- ultrahdr_metadata_ptr metadata,
- jr_compressed_ptr dest);
+ status_t appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
+ jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif, void* pIcc,
+ size_t icc_size, ultrahdr_metadata_ptr metadata, jr_compressed_ptr dest);
/*
* This method will tone map a HDR image to an SDR image.
*
- * @param src (input) uncompressed P010 image
- * @param dest (output) tone mapping result as a YUV_420 image
- * @return NO_ERROR if calculation succeeds, error code if error occurs.
+ * @param src pointer to uncompressed HDR image struct. HDR image is expected to be
+ * in p010 color format
+ * @param dest pointer to store tonemapped SDR image
*/
- status_t toneMap(jr_uncompressed_ptr src,
- jr_uncompressed_ptr dest);
+ status_t toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest);
/*
* This method will convert a YUV420 image from one YUV encoding to another in-place (eg.
@@ -396,15 +407,15 @@ private:
* @param dest_encoding output YUV encoding
* @return NO_ERROR if calculation succeeds, error code if error occurs.
*/
- status_t convertYuv(jr_uncompressed_ptr image,
- ultrahdr_color_gamut src_encoding,
+ status_t convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding,
ultrahdr_color_gamut dest_encoding);
/*
* This method will check the validity of the input arguments.
*
- * @param uncompressed_p010_image uncompressed HDR image in P010 color format
- * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+ * @param p010_image_ptr uncompressed HDR image in P010 color format
+ * @param yuv420_image_ptr pointer to uncompressed SDR image struct. HDR image is expected to
+ * be in 420p color format
* @param hdr_tf transfer function of the HDR image
* @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
* represents the maximum available size of the desitination buffer, and it must be
@@ -412,32 +423,30 @@ private:
* {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
* @return NO_ERROR if the input args are valid, error code is not valid.
*/
- status_t areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image,
- jr_uncompressed_ptr uncompressed_yuv_420_image,
- ultrahdr_transfer_function hdr_tf,
- jr_compressed_ptr dest);
+ status_t areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
+ jr_uncompressed_ptr yuv420_image_ptr,
+ ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest_ptr);
/*
* This method will check the validity of the input arguments.
*
- * @param uncompressed_p010_image uncompressed HDR image in P010 color format
- * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+ * @param p010_image_ptr uncompressed HDR image in P010 color format
+ * @param yuv420_image_ptr pointer to uncompressed SDR image struct. HDR image is expected to
+ * be in 420p color format
* @param hdr_tf transfer function of the HDR image
* @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
- * represents the maximum available size of the desitination buffer, and it must be
+ * represents the maximum available size of the destination buffer, and it must be
* set before calling this method. If the encoded JPEGR size exceeds
* {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
* @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
* the highest quality
* @return NO_ERROR if the input args are valid, error code is not valid.
*/
- status_t areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image,
- jr_uncompressed_ptr uncompressed_yuv_420_image,
- ultrahdr_transfer_function hdr_tf,
- jr_compressed_ptr dest,
- int quality);
+ status_t areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
+ jr_uncompressed_ptr yuv420_image_ptr,
+ ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest,
+ int quality);
};
-
} // namespace android::ultrahdr
#endif // ANDROID_ULTRAHDR_JPEGR_H
diff --git a/libs/ultrahdr/include/ultrahdr/ultrahdr.h b/libs/ultrahdr/include/ultrahdr/ultrahdr.h
index 17cc97173c..0252391a86 100644
--- a/libs/ultrahdr/include/ultrahdr/ultrahdr.h
+++ b/libs/ultrahdr/include/ultrahdr/ultrahdr.h
@@ -17,6 +17,8 @@
#ifndef ANDROID_ULTRAHDR_ULTRAHDR_H
#define ANDROID_ULTRAHDR_ULTRAHDR_H
+#include <string>
+
namespace android::ultrahdr {
// Color gamuts for image data
typedef enum {
@@ -28,6 +30,7 @@ typedef enum {
} ultrahdr_color_gamut;
// Transfer functions for image data
+// TODO: TF LINEAR is deprecated, remove this enum and the code surrounding it.
typedef enum {
ULTRAHDR_TF_UNSPECIFIED = -1,
ULTRAHDR_TF_LINEAR = 0,
diff --git a/libs/ultrahdr/jpegdecoderhelper.cpp b/libs/ultrahdr/jpegdecoderhelper.cpp
index fef544452a..2e7940cc2c 100644
--- a/libs/ultrahdr/jpegdecoderhelper.cpp
+++ b/libs/ultrahdr/jpegdecoderhelper.cpp
@@ -26,18 +26,23 @@ using namespace std;
namespace android::ultrahdr {
-#define ALIGNM(x, m) ((((x) + ((m) - 1)) / (m)) * (m))
+#define ALIGNM(x, m) ((((x) + ((m)-1)) / (m)) * (m))
-const uint32_t kAPP0Marker = JPEG_APP0; // JFIF
-const uint32_t kAPP1Marker = JPEG_APP0 + 1; // EXIF, XMP
-const uint32_t kAPP2Marker = JPEG_APP0 + 2; // ICC
+const uint32_t kAPP0Marker = JPEG_APP0; // JFIF
+const uint32_t kAPP1Marker = JPEG_APP0 + 1; // EXIF, XMP
+const uint32_t kAPP2Marker = JPEG_APP0 + 2; // ICC
-const std::string kXmpNameSpace = "http://ns.adobe.com/xap/1.0/";
-const std::string kExifIdCode = "Exif";
constexpr uint32_t kICCMarkerHeaderSize = 14;
constexpr uint8_t kICCSig[] = {
'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0',
};
+constexpr uint8_t kXmpNameSpace[] = {
+ 'h', 't', 't', 'p', ':', '/', '/', 'n', 's', '.', 'a', 'd', 'o', 'b', 'e',
+ '.', 'c', 'o', 'm', '/', 'x', 'a', 'p', '/', '1', '.', '0', '/', '\0',
+};
+constexpr uint8_t kExifIdCode[] = {
+ 'E', 'x', 'i', 'f', '\0', '\0',
+};
struct jpegr_source_mgr : jpeg_source_mgr {
jpegr_source_mgr(const uint8_t* ptr, int len);
@@ -76,8 +81,8 @@ static void jpegr_skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
static void jpegr_term_source(j_decompress_ptr /*cinfo*/) {}
-jpegr_source_mgr::jpegr_source_mgr(const uint8_t* ptr, int len) :
- mBufferPtr(ptr), mBufferLength(len) {
+jpegr_source_mgr::jpegr_source_mgr(const uint8_t* ptr, int len)
+ : mBufferPtr(ptr), mBufferLength(len) {
init_source = jpegr_init_source;
fill_input_buffer = jpegr_fill_input_buffer;
skip_input_data = jpegr_skip_input_data;
@@ -92,25 +97,18 @@ static void jpegrerror_exit(j_common_ptr cinfo) {
longjmp(err->setjmp_buffer, 1);
}
-JpegDecoderHelper::JpegDecoderHelper() {
-}
+JpegDecoderHelper::JpegDecoderHelper() {}
-JpegDecoderHelper::~JpegDecoderHelper() {
-}
+JpegDecoderHelper::~JpegDecoderHelper() {}
bool JpegDecoderHelper::decompressImage(const void* image, int length, bool decodeToRGBA) {
if (image == nullptr || length <= 0) {
ALOGE("Image size can not be handled: %d", length);
return false;
}
-
mResultBuffer.clear();
mXMPBuffer.clear();
- if (!decode(image, length, decodeToRGBA)) {
- return false;
- }
-
- return true;
+ return decode(image, length, decodeToRGBA);
}
void* JpegDecoderHelper::getDecompressedImagePtr() {
@@ -153,11 +151,15 @@ size_t JpegDecoderHelper::getDecompressedImageHeight() {
return mHeight;
}
-bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) {
+// Here we only handle the first EXIF package, and in theary EXIF (or JFIF) must be the first
+// in the image file.
+// We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package),
+// two bytes of package length which is stored in marker->original_length, and the real data
+// which is stored in marker->data.
+bool JpegDecoderHelper::extractEXIF(const void* image, int length) {
jpeg_decompress_struct cinfo;
jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
jpegrerror_mgr myerr;
- bool status = true;
cinfo.err = jpeg_std_error(&myerr.pub);
myerr.pub.error_exit = jpegrerror_exit;
@@ -170,11 +172,61 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA)
jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF);
jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
- jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
cinfo.src = &mgr;
jpeg_read_header(&cinfo, TRUE);
+ size_t pos = 2; // position after SOI
+ for (jpeg_marker_struct* marker = cinfo.marker_list;
+ marker;
+ marker = marker->next) {
+
+ pos += 4;
+ pos += marker->original_length;
+
+ if (marker->marker != kAPP1Marker) {
+ continue;
+ }
+
+ const unsigned int len = marker->data_length;
+
+ if (len > sizeof(kExifIdCode) &&
+ !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
+ mEXIFBuffer.resize(len, 0);
+ memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
+ mExifPos = pos - marker->original_length;
+ break;
+ }
+ }
+
+ jpeg_destroy_decompress(&cinfo);
+ return true;
+}
+
+bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) {
+ bool status = true;
+ jpeg_decompress_struct cinfo;
+ jpegrerror_mgr myerr;
+ cinfo.err = jpeg_std_error(&myerr.pub);
+ myerr.pub.error_exit = jpegrerror_exit;
+ if (setjmp(myerr.setjmp_buffer)) {
+ jpeg_destroy_decompress(&cinfo);
+ return false;
+ }
+
+ jpeg_create_decompress(&cinfo);
+
+ jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF);
+ jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
+ jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
+
+ jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
+ cinfo.src = &mgr;
+ if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
+ jpeg_destroy_decompress(&cinfo);
+ return false;
+ }
+
// Save XMP data, EXIF data, and ICC data.
// Here we only handle the first XMP / EXIF / ICC package.
// We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package),
@@ -183,30 +235,29 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA)
bool exifAppears = false;
bool xmpAppears = false;
bool iccAppears = false;
+ size_t pos = 2; // position after SOI
for (jpeg_marker_struct* marker = cinfo.marker_list;
marker && !(exifAppears && xmpAppears && iccAppears);
marker = marker->next) {
-
+ pos += 4;
+ pos += marker->original_length;
if (marker->marker != kAPP1Marker && marker->marker != kAPP2Marker) {
continue;
}
const unsigned int len = marker->data_length;
if (!xmpAppears &&
- len > kXmpNameSpace.size() &&
- !strncmp(reinterpret_cast<const char*>(marker->data),
- kXmpNameSpace.c_str(),
- kXmpNameSpace.size())) {
+ len > sizeof(kXmpNameSpace) &&
+ !memcmp(marker->data, kXmpNameSpace, sizeof(kXmpNameSpace))) {
mXMPBuffer.resize(len+1, 0);
memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len);
xmpAppears = true;
} else if (!exifAppears &&
- len > kExifIdCode.size() &&
- !strncmp(reinterpret_cast<const char*>(marker->data),
- kExifIdCode.c_str(),
- kExifIdCode.size())) {
+ len > sizeof(kExifIdCode) &&
+ !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
mEXIFBuffer.resize(len, 0);
memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
exifAppears = true;
+ mExifPos = pos - marker->original_length;
} else if (!iccAppears &&
len > sizeof(kICCSig) &&
!memcmp(marker->data, kICCSig, sizeof(kICCSig))) {
@@ -216,21 +267,25 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA)
}
}
- if (cinfo.image_width > kMaxWidth || cinfo.image_height > kMaxHeight) {
- // constraint on max width and max height is only due to alloc constraints
- // tune these values basing on the target device
+ mWidth = cinfo.image_width;
+ mHeight = cinfo.image_height;
+ if (mWidth > kMaxWidth || mHeight > kMaxHeight) {
status = false;
goto CleanUp;
}
- mWidth = cinfo.image_width;
- mHeight = cinfo.image_height;
-
if (decodeToRGBA) {
- if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
- // We don't intend to support decoding grayscale to RGBA
+ // The primary image is expected to be yuv420 sampling
+ if (cinfo.jpeg_color_space != JCS_YCbCr) {
+ status = false;
+ ALOGE("%s: decodeToRGBA unexpected jpeg color space ", __func__);
+ goto CleanUp;
+ }
+ if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 ||
+ cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 ||
+ cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) {
status = false;
- ALOGE("%s: decoding grayscale to RGBA is unsupported", __func__);
+ ALOGE("%s: decodeToRGBA unexpected primary image sub-sampling", __func__);
goto CleanUp;
}
// 4 bytes per pixel
@@ -238,12 +293,9 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA)
cinfo.out_color_space = JCS_EXT_RGBA;
} else {
if (cinfo.jpeg_color_space == JCS_YCbCr) {
- if (cinfo.comp_info[0].h_samp_factor != 2 ||
- cinfo.comp_info[1].h_samp_factor != 1 ||
- cinfo.comp_info[2].h_samp_factor != 1 ||
- cinfo.comp_info[0].v_samp_factor != 2 ||
- cinfo.comp_info[1].v_samp_factor != 1 ||
- cinfo.comp_info[2].v_samp_factor != 1) {
+ if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 ||
+ cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 ||
+ cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) {
status = false;
ALOGE("%s: decoding to YUV only supports 4:2:0 subsampling", __func__);
goto CleanUp;
@@ -251,17 +303,19 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA)
mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0);
} else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0);
+ } else {
+ status = false;
+ ALOGE("%s: decodeToYUV unexpected jpeg color space", __func__);
+ goto CleanUp;
}
cinfo.out_color_space = cinfo.jpeg_color_space;
cinfo.raw_data_out = TRUE;
}
- cinfo.dct_method = JDCT_IFAST;
-
+ cinfo.dct_method = JDCT_ISLOW;
jpeg_start_decompress(&cinfo);
-
if (!decompress(&cinfo, static_cast<const uint8_t*>(mResultBuffer.data()),
- cinfo.jpeg_color_space == JCS_GRAYSCALE)) {
+ cinfo.jpeg_color_space == JCS_GRAYSCALE)) {
status = false;
goto CleanUp;
}
@@ -274,25 +328,20 @@ CleanUp:
}
bool JpegDecoderHelper::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest,
- bool isSingleChannel) {
- if (isSingleChannel) {
- return decompressSingleChannel(cinfo, dest);
- }
- if (cinfo->out_color_space == JCS_EXT_RGBA)
- return decompressRGBA(cinfo, dest);
- else
- return decompressYUV(cinfo, dest);
+ bool isSingleChannel) {
+ return isSingleChannel
+ ? decompressSingleChannel(cinfo, dest)
+ : ((cinfo->out_color_space == JCS_EXT_RGBA) ? decompressRGBA(cinfo, dest)
+ : decompressYUV(cinfo, dest));
}
-bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int length,
- size_t *pWidth, size_t *pHeight,
- std::vector<uint8_t> *iccData , std::vector<uint8_t> *exifData) {
+bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int length, size_t* pWidth,
+ size_t* pHeight, std::vector<uint8_t>* iccData,
+ std::vector<uint8_t>* exifData) {
jpeg_decompress_struct cinfo;
- jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
jpegrerror_mgr myerr;
cinfo.err = jpeg_std_error(&myerr.pub);
myerr.pub.error_exit = jpegrerror_exit;
-
if (setjmp(myerr.setjmp_buffer)) {
jpeg_destroy_decompress(&cinfo);
return false;
@@ -302,6 +351,7 @@ bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int leng
jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
+ jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
cinfo.src = &mgr;
if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
jpeg_destroy_decompress(&cinfo);
@@ -316,8 +366,7 @@ bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int leng
}
if (iccData != nullptr) {
- for (jpeg_marker_struct* marker = cinfo.marker_list; marker;
- marker = marker->next) {
+ for (jpeg_marker_struct* marker = cinfo.marker_list; marker; marker = marker->next) {
if (marker->marker != kAPP2Marker) {
continue;
}
@@ -339,9 +388,8 @@ bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int leng
}
const unsigned int len = marker->data_length;
- if (len >= kExifIdCode.size() &&
- !strncmp(reinterpret_cast<const char*>(marker->data), kExifIdCode.c_str(),
- kExifIdCode.size())) {
+ if (len >= sizeof(kExifIdCode) &&
+ !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
exifData->resize(len, 0);
memcpy(static_cast<void*>(exifData->data()), marker->data, len);
exifAppears = true;
@@ -354,25 +402,20 @@ bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int leng
}
bool JpegDecoderHelper::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
- JSAMPLE* decodeDst = (JSAMPLE*) dest;
- uint32_t lines = 0;
- // TODO: use batches for more effectiveness
- while (lines < cinfo->image_height) {
- uint32_t ret = jpeg_read_scanlines(cinfo, &decodeDst, 1);
- if (ret == 0) {
- break;
- }
- decodeDst += cinfo->image_width * 4;
- lines++;
+ JSAMPLE* out = (JSAMPLE*)dest;
+
+ while (cinfo->output_scanline < cinfo->image_height) {
+ if (1 != jpeg_read_scanlines(cinfo, &out, 1)) return false;
+ out += cinfo->image_width * 4;
}
- return lines == cinfo->image_height;
+ return true;
}
bool JpegDecoderHelper::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
JSAMPROW y[kCompressBatchSize];
JSAMPROW cb[kCompressBatchSize / 2];
JSAMPROW cr[kCompressBatchSize / 2];
- JSAMPARRAY planes[3] {y, cb, cr};
+ JSAMPARRAY planes[3]{y, cb, cr};
size_t y_plane_size = cinfo->image_width * cinfo->image_height;
size_t uv_plane_size = y_plane_size / 4;
@@ -391,7 +434,7 @@ bool JpegDecoderHelper::decompressYUV(jpeg_decompress_struct* cinfo, const uint8
JSAMPROW y_intrm[kCompressBatchSize];
JSAMPROW cb_intrm[kCompressBatchSize / 2];
JSAMPROW cr_intrm[kCompressBatchSize / 2];
- JSAMPARRAY planes_intrm[3] {y_intrm, cb_intrm, cr_intrm};
+ JSAMPARRAY planes_intrm[3]{y_intrm, cb_intrm, cr_intrm};
if (!is_width_aligned) {
size_t mcu_row_size = aligned_width * kCompressBatchSize * 3 / 2;
buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
@@ -448,9 +491,10 @@ bool JpegDecoderHelper::decompressYUV(jpeg_decompress_struct* cinfo, const uint8
return true;
}
-bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
+bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo,
+ const uint8_t* dest) {
JSAMPROW y[kCompressBatchSize];
- JSAMPARRAY planes[1] {y};
+ JSAMPARRAY planes[1]{y};
uint8_t* y_plane = const_cast<uint8_t*>(dest);
std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
@@ -461,7 +505,7 @@ bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo, c
std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
uint8_t* y_plane_intrm = nullptr;
JSAMPROW y_intrm[kCompressBatchSize];
- JSAMPARRAY planes_intrm[1] {y_intrm};
+ JSAMPARRAY planes_intrm[1]{y_intrm};
if (!is_width_aligned) {
size_t mcu_row_size = aligned_width * kCompressBatchSize;
buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
@@ -496,4 +540,4 @@ bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo, c
return true;
}
-} // namespace ultrahdr
+} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/jpegencoderhelper.cpp b/libs/ultrahdr/jpegencoderhelper.cpp
index a03547b538..13ae7424d5 100644
--- a/libs/ultrahdr/jpegencoderhelper.cpp
+++ b/libs/ultrahdr/jpegencoderhelper.cpp
@@ -14,38 +14,35 @@
* limitations under the License.
*/
-#include <ultrahdr/jpegencoderhelper.h>
+#include <cstring>
+#include <memory>
+#include <vector>
+#include <ultrahdr/jpegencoderhelper.h>
#include <utils/Log.h>
-#include <errno.h>
-
namespace android::ultrahdr {
-#define ALIGNM(x, m) ((((x) + ((m) - 1)) / (m)) * (m))
-
// The destination manager that can access |mResultBuffer| in JpegEncoderHelper.
struct destination_mgr {
-public:
struct jpeg_destination_mgr mgr;
JpegEncoderHelper* encoder;
};
-JpegEncoderHelper::JpegEncoderHelper() {
-}
+JpegEncoderHelper::JpegEncoderHelper() {}
-JpegEncoderHelper::~JpegEncoderHelper() {
-}
+JpegEncoderHelper::~JpegEncoderHelper() {}
-bool JpegEncoderHelper::compressImage(const void* image, int width, int height, int quality,
- const void* iccBuffer, unsigned int iccSize,
- bool isSingleChannel) {
+bool JpegEncoderHelper::compressImage(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width,
+ int height, int lumaStride, int chromaStride, int quality,
+ const void* iccBuffer, unsigned int iccSize) {
mResultBuffer.clear();
- if (!encode(image, width, height, quality, iccBuffer, iccSize, isSingleChannel)) {
+ if (!encode(yBuffer, uvBuffer, width, height, lumaStride, chromaStride, quality, iccBuffer,
+ iccSize)) {
return false;
}
- ALOGI("Compressed JPEG: %d[%dx%d] -> %zu bytes",
- (width * height * 12) / 8, width, height, mResultBuffer.size());
+ ALOGI("Compressed JPEG: %d[%dx%d] -> %zu bytes", (width * height * 12) / 8, width, height,
+ mResultBuffer.size());
return true;
}
@@ -85,29 +82,28 @@ void JpegEncoderHelper::outputErrorMessage(j_common_ptr cinfo) {
char buffer[JMSG_LENGTH_MAX];
/* Create the message */
- (*cinfo->err->format_message) (cinfo, buffer);
+ (*cinfo->err->format_message)(cinfo, buffer);
ALOGE("%s\n", buffer);
}
-bool JpegEncoderHelper::encode(const void* image, int width, int height, int jpegQuality,
- const void* iccBuffer, unsigned int iccSize, bool isSingleChannel) {
+bool JpegEncoderHelper::encode(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width,
+ int height, int lumaStride, int chromaStride, int quality,
+ const void* iccBuffer, unsigned int iccSize) {
jpeg_compress_struct cinfo;
jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
- // Override output_message() to print error log with ALOGE().
cinfo.err->output_message = &outputErrorMessage;
jpeg_create_compress(&cinfo);
setJpegDestination(&cinfo);
-
- setJpegCompressStruct(width, height, jpegQuality, &cinfo, isSingleChannel);
+ setJpegCompressStruct(width, height, quality, &cinfo, uvBuffer == nullptr);
jpeg_start_compress(&cinfo, TRUE);
-
if (iccBuffer != nullptr && iccSize > 0) {
jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast<const JOCTET*>(iccBuffer), iccSize);
}
-
- bool status = compress(&cinfo, static_cast<const uint8_t*>(image), isSingleChannel);
+ bool status = cinfo.num_components == 1
+ ? compressY(&cinfo, yBuffer, lumaStride)
+ : compressYuv(&cinfo, yBuffer, uvBuffer, lumaStride, chromaStride);
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
@@ -115,8 +111,9 @@ bool JpegEncoderHelper::encode(const void* image, int width, int height, int jpe
}
void JpegEncoderHelper::setJpegDestination(jpeg_compress_struct* cinfo) {
- destination_mgr* dest = static_cast<struct destination_mgr *>((*cinfo->mem->alloc_small) (
- (j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(destination_mgr)));
+ destination_mgr* dest = static_cast<struct destination_mgr*>(
+ (*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_PERMANENT,
+ sizeof(destination_mgr)));
dest->encoder = this;
dest->mgr.init_destination = &initDestination;
dest->mgr.empty_output_buffer = &emptyOutputBuffer;
@@ -125,59 +122,40 @@ void JpegEncoderHelper::setJpegDestination(jpeg_compress_struct* cinfo) {
}
void JpegEncoderHelper::setJpegCompressStruct(int width, int height, int quality,
- jpeg_compress_struct* cinfo, bool isSingleChannel) {
+ jpeg_compress_struct* cinfo, bool isSingleChannel) {
cinfo->image_width = width;
cinfo->image_height = height;
- if (isSingleChannel) {
- cinfo->input_components = 1;
- cinfo->in_color_space = JCS_GRAYSCALE;
- } else {
- cinfo->input_components = 3;
- cinfo->in_color_space = JCS_YCbCr;
- }
+ cinfo->input_components = isSingleChannel ? 1 : 3;
+ cinfo->in_color_space = isSingleChannel ? JCS_GRAYSCALE : JCS_YCbCr;
jpeg_set_defaults(cinfo);
-
jpeg_set_quality(cinfo, quality, TRUE);
- jpeg_set_colorspace(cinfo, isSingleChannel ? JCS_GRAYSCALE : JCS_YCbCr);
cinfo->raw_data_in = TRUE;
- cinfo->dct_method = JDCT_IFAST;
-
- if (!isSingleChannel) {
- // Configure sampling factors. The sampling factor is JPEG subsampling 420 because the
- // source format is YUV420.
- cinfo->comp_info[0].h_samp_factor = 2;
- cinfo->comp_info[0].v_samp_factor = 2;
- cinfo->comp_info[1].h_samp_factor = 1;
- cinfo->comp_info[1].v_samp_factor = 1;
- cinfo->comp_info[2].h_samp_factor = 1;
- cinfo->comp_info[2].v_samp_factor = 1;
- }
-}
-
-bool JpegEncoderHelper::compress(
- jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel) {
- if (isSingleChannel) {
- return compressSingleChannel(cinfo, image);
+ cinfo->dct_method = JDCT_ISLOW;
+ cinfo->comp_info[0].h_samp_factor = cinfo->in_color_space == JCS_GRAYSCALE ? 1 : 2;
+ cinfo->comp_info[0].v_samp_factor = cinfo->in_color_space == JCS_GRAYSCALE ? 1 : 2;
+ for (int i = 1; i < cinfo->num_components; i++) {
+ cinfo->comp_info[i].h_samp_factor = 1;
+ cinfo->comp_info[i].v_samp_factor = 1;
}
- return compressYuv(cinfo, image);
}
-bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv) {
+bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yBuffer,
+ const uint8_t* uvBuffer, int lumaStride, int chromaStride) {
JSAMPROW y[kCompressBatchSize];
JSAMPROW cb[kCompressBatchSize / 2];
JSAMPROW cr[kCompressBatchSize / 2];
- JSAMPARRAY planes[3] {y, cb, cr};
+ JSAMPARRAY planes[3]{y, cb, cr};
- size_t y_plane_size = cinfo->image_width * cinfo->image_height;
- size_t uv_plane_size = y_plane_size / 4;
- uint8_t* y_plane = const_cast<uint8_t*>(yuv);
- uint8_t* u_plane = const_cast<uint8_t*>(yuv + y_plane_size);
- uint8_t* v_plane = const_cast<uint8_t*>(yuv + y_plane_size + uv_plane_size);
+ size_t y_plane_size = lumaStride * cinfo->image_height;
+ size_t u_plane_size = chromaStride * cinfo->image_height / 2;
+ uint8_t* y_plane = const_cast<uint8_t*>(yBuffer);
+ uint8_t* u_plane = const_cast<uint8_t*>(uvBuffer);
+ uint8_t* v_plane = const_cast<uint8_t*>(u_plane + u_plane_size);
std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
memset(empty.get(), 0, cinfo->image_width);
const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
- const bool is_width_aligned = (aligned_width == cinfo->image_width);
+ const bool need_padding = (lumaStride < aligned_width);
std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
uint8_t* y_plane_intrm = nullptr;
uint8_t* u_plane_intrm = nullptr;
@@ -186,7 +164,7 @@ bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t*
JSAMPROW cb_intrm[kCompressBatchSize / 2];
JSAMPROW cr_intrm[kCompressBatchSize / 2];
JSAMPARRAY planes_intrm[3]{y_intrm, cb_intrm, cr_intrm};
- if (!is_width_aligned) {
+ if (need_padding) {
size_t mcu_row_size = aligned_width * kCompressBatchSize * 3 / 2;
buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
y_plane_intrm = buffer_intrm.get();
@@ -211,11 +189,11 @@ bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t*
for (int i = 0; i < kCompressBatchSize; ++i) {
size_t scanline = cinfo->next_scanline + i;
if (scanline < cinfo->image_height) {
- y[i] = y_plane + scanline * cinfo->image_width;
+ y[i] = y_plane + scanline * lumaStride;
} else {
y[i] = empty.get();
}
- if (!is_width_aligned) {
+ if (need_padding) {
memcpy(y_intrm[i], y[i], cinfo->image_width);
}
}
@@ -223,18 +201,18 @@ bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t*
for (int i = 0; i < kCompressBatchSize / 2; ++i) {
size_t scanline = cinfo->next_scanline / 2 + i;
if (scanline < cinfo->image_height / 2) {
- int offset = scanline * (cinfo->image_width / 2);
+ int offset = scanline * chromaStride;
cb[i] = u_plane + offset;
cr[i] = v_plane + offset;
} else {
cb[i] = cr[i] = empty.get();
}
- if (!is_width_aligned) {
+ if (need_padding) {
memcpy(cb_intrm[i], cb[i], cinfo->image_width / 2);
memcpy(cr_intrm[i], cr[i], cinfo->image_width / 2);
}
}
- int processed = jpeg_write_raw_data(cinfo, is_width_aligned ? planes : planes_intrm,
+ int processed = jpeg_write_raw_data(cinfo, need_padding ? planes_intrm : planes,
kCompressBatchSize);
if (processed != kCompressBatchSize) {
ALOGE("Number of processed lines does not equal input lines.");
@@ -244,22 +222,23 @@ bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t*
return true;
}
-bool JpegEncoderHelper::compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image) {
+bool JpegEncoderHelper::compressY(jpeg_compress_struct* cinfo, const uint8_t* yBuffer,
+ int lumaStride) {
JSAMPROW y[kCompressBatchSize];
- JSAMPARRAY planes[1] {y};
+ JSAMPARRAY planes[1]{y};
- uint8_t* y_plane = const_cast<uint8_t*>(image);
+ uint8_t* y_plane = const_cast<uint8_t*>(yBuffer);
std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
memset(empty.get(), 0, cinfo->image_width);
const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
- bool is_width_aligned = (aligned_width == cinfo->image_width);
+ const bool need_padding = (lumaStride < aligned_width);
std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
uint8_t* y_plane_intrm = nullptr;
uint8_t* u_plane_intrm = nullptr;
JSAMPROW y_intrm[kCompressBatchSize];
JSAMPARRAY planes_intrm[]{y_intrm};
- if (!is_width_aligned) {
+ if (need_padding) {
size_t mcu_row_size = aligned_width * kCompressBatchSize;
buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
y_plane_intrm = buffer_intrm.get();
@@ -273,15 +252,15 @@ bool JpegEncoderHelper::compressSingleChannel(jpeg_compress_struct* cinfo, const
for (int i = 0; i < kCompressBatchSize; ++i) {
size_t scanline = cinfo->next_scanline + i;
if (scanline < cinfo->image_height) {
- y[i] = y_plane + scanline * cinfo->image_width;
+ y[i] = y_plane + scanline * lumaStride;
} else {
y[i] = empty.get();
}
- if (!is_width_aligned) {
+ if (need_padding) {
memcpy(y_intrm[i], y[i], cinfo->image_width);
}
}
- int processed = jpeg_write_raw_data(cinfo, is_width_aligned ? planes : planes_intrm,
+ int processed = jpeg_write_raw_data(cinfo, need_padding ? planes_intrm : planes,
kCompressBatchSize);
if (processed != kCompressBatchSize / 2) {
ALOGE("Number of processed lines does not equal input lines.");
@@ -291,4 +270,4 @@ bool JpegEncoderHelper::compressSingleChannel(jpeg_compress_struct* cinfo, const
return true;
}
-} // namespace ultrahdr
+} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp
index fb24c9d206..3d70fcea71 100644
--- a/libs/ultrahdr/jpegr.cpp
+++ b/libs/ultrahdr/jpegr.cpp
@@ -14,31 +14,26 @@
* limitations under the License.
*/
-#include <ultrahdr/jpegr.h>
-#include <ultrahdr/jpegencoderhelper.h>
-#include <ultrahdr/jpegdecoderhelper.h>
+#include <cmath>
+#include <condition_variable>
+#include <deque>
+#include <memory>
+#include <mutex>
+#include <thread>
+
#include <ultrahdr/gainmapmath.h>
+#include <ultrahdr/icc.h>
+#include <ultrahdr/jpegr.h>
#include <ultrahdr/jpegrutils.h>
#include <ultrahdr/multipictureformat.h>
-#include <ultrahdr/icc.h>
-#include <image_io/jpeg/jpeg_marker.h>
+#include <image_io/base/data_segment_data_source.h>
#include <image_io/jpeg/jpeg_info.h>
-#include <image_io/jpeg/jpeg_scanner.h>
#include <image_io/jpeg/jpeg_info_builder.h>
-#include <image_io/base/data_segment_data_source.h>
-#include <utils/Log.h>
+#include <image_io/jpeg/jpeg_marker.h>
+#include <image_io/jpeg/jpeg_scanner.h>
-#include <map>
-#include <memory>
-#include <sstream>
-#include <string>
-#include <cmath>
-#include <condition_variable>
-#include <deque>
-#include <mutex>
-#include <thread>
-#include <unistd.h>
+#include <utils/Log.h>
using namespace std;
using namespace photos_editing_formats::image_io;
@@ -60,25 +55,6 @@ namespace android::ultrahdr {
} \
}
-// The current JPEGR version that we encode to
-static const char* const kJpegrVersion = "1.0";
-
-// Map is quarter res / sixteenth size
-static const size_t kMapDimensionScaleFactor = 4;
-
-// Gain Map width is (image_width / kMapDimensionScaleFactor). If we were to
-// compress 420 GainMap in jpeg, then we need at least 2 samples. For Grayscale
-// 1 sample is sufficient. We are using 2 here anyways
-static const int kMinWidth = 2 * kMapDimensionScaleFactor;
-static const int kMinHeight = 2 * kMapDimensionScaleFactor;
-
-// JPEG block size.
-// JPEG encoding / decoding will require block based DCT transform 16 x 16 for luma,
-// and 8 x 8 for chroma.
-// Width must be 16 dividable for luma, and 8 dividable for chroma.
-// If this criteria is not facilitated, we will pad zeros based to each line on the
-// required block size.
-static const size_t kJpegBlock = JpegEncoderHelper::kCompressBatchSize;
// JPEG compress quality (0 ~ 100) for gain map
static const int kMapCompressQuality = 85;
@@ -96,184 +72,200 @@ int GetCPUCoreCount() {
return cpuCoreCount;
}
-status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image,
- jr_uncompressed_ptr uncompressed_yuv_420_image,
+/*
+ * Helper function copies the JPEG image from without EXIF.
+ *
+ * @param pDest destination of the data to be written.
+ * @param pSource source of data being written.
+ * @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos().
+ * (4 bytes offset to FF sign, the byte after FF E1 XX XX <this byte>).
+ * @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize().
+ */
+static void copyJpegWithoutExif(jr_compressed_ptr pDest,
+ jr_compressed_ptr pSource,
+ size_t exif_pos,
+ size_t exif_size) {
+ const size_t exif_offset = 4; //exif_pos has 4 bytes offset to the FF sign
+ pDest->length = pSource->length - exif_size - exif_offset;
+ pDest->data = new uint8_t[pDest->length];
+ pDest->maxLength = pDest->length;
+ pDest->colorGamut = pSource->colorGamut;
+ memcpy(pDest->data, pSource->data, exif_pos - exif_offset);
+ memcpy((uint8_t*)pDest->data + exif_pos - exif_offset,
+ (uint8_t*)pSource->data + exif_pos + exif_size,
+ pSource->length - exif_pos - exif_size);
+}
+
+status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
+ jr_uncompressed_ptr yuv420_image_ptr,
ultrahdr_transfer_function hdr_tf,
- jr_compressed_ptr dest) {
- if (uncompressed_p010_image == nullptr || uncompressed_p010_image->data == nullptr) {
- ALOGE("received nullptr for uncompressed p010 image");
+ jr_compressed_ptr dest_ptr) {
+ if (p010_image_ptr == nullptr || p010_image_ptr->data == nullptr) {
+ ALOGE("Received nullptr for input p010 image");
return ERROR_JPEGR_INVALID_NULL_PTR;
}
-
- if (uncompressed_p010_image->width % 2 != 0
- || uncompressed_p010_image->height % 2 != 0) {
- ALOGE("Image dimensions cannot be odd, image dimensions %dx%d",
- uncompressed_p010_image->width, uncompressed_p010_image->height);
+ if (p010_image_ptr->width % 2 != 0 || p010_image_ptr->height % 2 != 0) {
+ ALOGE("Image dimensions cannot be odd, image dimensions %dx%d", p010_image_ptr->width,
+ p010_image_ptr->height);
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
-
- if (uncompressed_p010_image->width < kMinWidth
- || uncompressed_p010_image->height < kMinHeight) {
- ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %dx%d",
- kMinWidth, kMinHeight, uncompressed_p010_image->width, uncompressed_p010_image->height);
+ if (p010_image_ptr->width < kMinWidth || p010_image_ptr->height < kMinHeight) {
+ ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %dx%d", kMinWidth,
+ kMinHeight, p010_image_ptr->width, p010_image_ptr->height);
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
-
- if (uncompressed_p010_image->width > kMaxWidth
- || uncompressed_p010_image->height > kMaxHeight) {
- ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %dx%d",
- kMaxWidth, kMaxHeight, uncompressed_p010_image->width, uncompressed_p010_image->height);
+ if (p010_image_ptr->width > kMaxWidth || p010_image_ptr->height > kMaxHeight) {
+ ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %dx%d", kMaxWidth,
+ kMaxHeight, p010_image_ptr->width, p010_image_ptr->height);
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
-
- if (uncompressed_p010_image->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED
- || uncompressed_p010_image->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
- ALOGE("Unrecognized p010 color gamut %d", uncompressed_p010_image->colorGamut);
+ if (p010_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
+ p010_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
+ ALOGE("Unrecognized p010 color gamut %d", p010_image_ptr->colorGamut);
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
-
- if (uncompressed_p010_image->luma_stride != 0
- && uncompressed_p010_image->luma_stride < uncompressed_p010_image->width) {
- ALOGE("Luma stride can not be smaller than width, stride=%d, width=%d",
- uncompressed_p010_image->luma_stride, uncompressed_p010_image->width);
+ if (p010_image_ptr->luma_stride != 0 && p010_image_ptr->luma_stride < p010_image_ptr->width) {
+ ALOGE("Luma stride must not be smaller than width, stride=%d, width=%d",
+ p010_image_ptr->luma_stride, p010_image_ptr->width);
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
-
- if (uncompressed_p010_image->chroma_data != nullptr
- && uncompressed_p010_image->chroma_stride < uncompressed_p010_image->width) {
- ALOGE("Chroma stride can not be smaller than width, stride=%d, width=%d",
- uncompressed_p010_image->chroma_stride,
- uncompressed_p010_image->width);
+ if (p010_image_ptr->chroma_data != nullptr &&
+ p010_image_ptr->chroma_stride < p010_image_ptr->width) {
+ ALOGE("Chroma stride must not be smaller than width, stride=%d, width=%d",
+ p010_image_ptr->chroma_stride, p010_image_ptr->width);
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
-
- if (dest == nullptr || dest->data == nullptr) {
- ALOGE("received nullptr for destination");
+ if (dest_ptr == nullptr || dest_ptr->data == nullptr) {
+ ALOGE("Received nullptr for destination");
return ERROR_JPEGR_INVALID_NULL_PTR;
}
-
- if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX
- || hdr_tf == ULTRAHDR_TF_SRGB) {
+ if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX || hdr_tf == ULTRAHDR_TF_SRGB) {
ALOGE("Invalid hdr transfer function %d", hdr_tf);
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
-
- if (uncompressed_yuv_420_image == nullptr) {
+ if (yuv420_image_ptr == nullptr) {
return NO_ERROR;
}
-
- if (uncompressed_yuv_420_image->data == nullptr) {
- ALOGE("received nullptr for uncompressed 420 image");
+ if (yuv420_image_ptr->data == nullptr) {
+ ALOGE("Received nullptr for uncompressed 420 image");
return ERROR_JPEGR_INVALID_NULL_PTR;
}
-
- if (uncompressed_yuv_420_image->luma_stride != 0) {
- ALOGE("Stride is not supported for YUV420 image");
- return ERROR_JPEGR_UNSUPPORTED_FEATURE;
+ if (yuv420_image_ptr->luma_stride != 0 &&
+ yuv420_image_ptr->luma_stride < yuv420_image_ptr->width) {
+ ALOGE("Luma stride must not be smaller than width, stride=%d, width=%d",
+ yuv420_image_ptr->luma_stride, yuv420_image_ptr->width);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
-
- if (uncompressed_yuv_420_image->chroma_data != nullptr) {
- ALOGE("Pointer to chroma plane is not supported for YUV420 image, chroma data must"
- "be immediately after the luma data.");
- return ERROR_JPEGR_UNSUPPORTED_FEATURE;
+ if (yuv420_image_ptr->chroma_data != nullptr &&
+ yuv420_image_ptr->chroma_stride < yuv420_image_ptr->width / 2) {
+ ALOGE("Chroma stride must not be smaller than (width / 2), stride=%d, width=%d",
+ yuv420_image_ptr->chroma_stride, yuv420_image_ptr->width);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
-
- if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
- || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
- ALOGE("Image resolutions mismatch: P010: %dx%d, YUV420: %dx%d",
- uncompressed_p010_image->width,
- uncompressed_p010_image->height,
- uncompressed_yuv_420_image->width,
- uncompressed_yuv_420_image->height);
+ if (p010_image_ptr->width != yuv420_image_ptr->width ||
+ p010_image_ptr->height != yuv420_image_ptr->height) {
+ ALOGE("Image resolutions mismatch: P010: %dx%d, YUV420: %dx%d", p010_image_ptr->width,
+ p010_image_ptr->height, yuv420_image_ptr->width, yuv420_image_ptr->height);
return ERROR_JPEGR_RESOLUTION_MISMATCH;
}
-
- if (uncompressed_yuv_420_image->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED
- || uncompressed_yuv_420_image->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
- ALOGE("Unrecognized 420 color gamut %d", uncompressed_yuv_420_image->colorGamut);
+ if (yuv420_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
+ yuv420_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
+ ALOGE("Unrecognized 420 color gamut %d", yuv420_image_ptr->colorGamut);
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
-
return NO_ERROR;
}
-status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image,
- jr_uncompressed_ptr uncompressed_yuv_420_image,
+status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
+ jr_uncompressed_ptr yuv420_image_ptr,
ultrahdr_transfer_function hdr_tf,
- jr_compressed_ptr dest,
- int quality) {
- if (status_t ret = areInputArgumentsValid(
- uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf, dest) != NO_ERROR) {
- return ret;
- }
-
+ jr_compressed_ptr dest_ptr, int quality) {
if (quality < 0 || quality > 100) {
ALOGE("quality factor is out side range [0-100], quality factor : %d", quality);
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
-
- return NO_ERROR;
+ return areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest_ptr);
}
/* Encode API-0 */
-status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
- ultrahdr_transfer_function hdr_tf,
- jr_compressed_ptr dest,
- int quality,
- jr_exif_ptr exif) {
- if (status_t ret = areInputArgumentsValid(
- uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr,
- hdr_tf, dest, quality) != NO_ERROR) {
+status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
+ jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
+ // validate input arguments
+ if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest, quality);
+ ret != NO_ERROR) {
return ret;
}
-
if (exif != nullptr && exif->data == nullptr) {
ALOGE("received nullptr for exif metadata");
return ERROR_JPEGR_INVALID_NULL_PTR;
}
- ultrahdr_metadata_struct metadata;
- metadata.version = kJpegrVersion;
-
- jpegr_uncompressed_struct uncompressed_yuv_420_image;
- unique_ptr<uint8_t[]> uncompressed_yuv_420_image_data = make_unique<uint8_t[]>(
- uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2);
- uncompressed_yuv_420_image.data = uncompressed_yuv_420_image_data.get();
- JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image));
-
- jpegr_uncompressed_struct map;
- JPEGR_CHECK(generateGainMap(
- &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
+ // clean up input structure for later usage
+ jpegr_uncompressed_struct p010_image = *p010_image_ptr;
+ if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
+ if (!p010_image.chroma_data) {
+ uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
+ p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
+ p010_image.chroma_stride = p010_image.luma_stride;
+ }
+
+ const int yu420_luma_stride = ALIGNM(p010_image.width, kJpegBlock);
+ unique_ptr<uint8_t[]> yuv420_image_data =
+ make_unique<uint8_t[]>(yu420_luma_stride * p010_image.height * 3 / 2);
+ jpegr_uncompressed_struct yuv420_image = {.data = yuv420_image_data.get(),
+ .width = p010_image.width,
+ .height = p010_image.height,
+ .colorGamut = p010_image.colorGamut,
+ .luma_stride = yu420_luma_stride,
+ .chroma_data = nullptr,
+ .chroma_stride = yu420_luma_stride >> 1};
+ uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
+ yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
+
+ // tone map
+ JPEGR_CHECK(toneMap(&p010_image, &yuv420_image));
+
+ // gain map
+ ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
+ jpegr_uncompressed_struct gainmap_image;
+ JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
std::unique_ptr<uint8_t[]> map_data;
- map_data.reset(reinterpret_cast<uint8_t*>(map.data));
-
- JpegEncoderHelper jpeg_encoder_gainmap;
- JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap));
- jpegr_compressed_struct compressed_map;
- compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize();
- compressed_map.length = compressed_map.maxLength;
- compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
- compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-
- sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
- uncompressed_yuv_420_image.colorGamut);
-
- // Convert to Bt601 YUV encoding for JPEG encode
- JPEGR_CHECK(convertYuv(&uncompressed_yuv_420_image, uncompressed_yuv_420_image.colorGamut,
- ULTRAHDR_COLORGAMUT_P3));
-
- JpegEncoderHelper jpeg_encoder;
- if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data,
- uncompressed_yuv_420_image.width,
- uncompressed_yuv_420_image.height, quality,
- icc->getData(), icc->getLength())) {
+ map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
+
+ // compress gain map
+ JpegEncoderHelper jpeg_enc_obj_gm;
+ JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
+ jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
+ .length = static_cast<int>(
+ jpeg_enc_obj_gm.getCompressedImageSize()),
+ .maxLength = static_cast<int>(
+ jpeg_enc_obj_gm.getCompressedImageSize()),
+ .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
+
+ sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut);
+
+ // convert to Bt601 YUV encoding for JPEG encode
+ if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) {
+ JPEGR_CHECK(convertYuv(&yuv420_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
+ }
+
+ // compress 420 image
+ JpegEncoderHelper jpeg_enc_obj_yuv420;
+ if (!jpeg_enc_obj_yuv420.compressImage(reinterpret_cast<uint8_t*>(yuv420_image.data),
+ reinterpret_cast<uint8_t*>(yuv420_image.chroma_data),
+ yuv420_image.width, yuv420_image.height,
+ yuv420_image.luma_stride, yuv420_image.chroma_stride,
+ quality, icc->getData(), icc->getLength())) {
return ERROR_JPEGR_ENCODE_ERROR;
}
- jpegr_compressed_struct jpeg;
- jpeg.data = jpeg_encoder.getCompressedImagePtr();
- jpeg.length = jpeg_encoder.getCompressedImageSize();
+ jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(),
+ .length = static_cast<int>(
+ jpeg_enc_obj_yuv420.getCompressedImageSize()),
+ .maxLength = static_cast<int>(
+ jpeg_enc_obj_yuv420.getCompressedImageSize()),
+ .colorGamut = yuv420_image.colorGamut};
- // No ICC since JPEG encode already did it
+ // append gain map, no ICC since JPEG encode already did it
JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
&metadata, dest));
@@ -281,226 +273,277 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
}
/* Encode API-1 */
-status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
- jr_uncompressed_ptr uncompressed_yuv_420_image,
- ultrahdr_transfer_function hdr_tf,
- jr_compressed_ptr dest,
- int quality,
- jr_exif_ptr exif) {
- if (uncompressed_yuv_420_image == nullptr) {
+status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
+ jr_uncompressed_ptr yuv420_image_ptr, ultrahdr_transfer_function hdr_tf,
+ jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
+ // validate input arguments
+ if (yuv420_image_ptr == nullptr) {
ALOGE("received nullptr for uncompressed 420 image");
return ERROR_JPEGR_INVALID_NULL_PTR;
}
-
if (exif != nullptr && exif->data == nullptr) {
ALOGE("received nullptr for exif metadata");
return ERROR_JPEGR_INVALID_NULL_PTR;
}
-
- if (status_t ret = areInputArgumentsValid(
- uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf,
- dest, quality) != NO_ERROR) {
+ if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest, quality);
+ ret != NO_ERROR) {
return ret;
}
- ultrahdr_metadata_struct metadata;
- metadata.version = kJpegrVersion;
+ // clean up input structure for later usage
+ jpegr_uncompressed_struct p010_image = *p010_image_ptr;
+ if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
+ if (!p010_image.chroma_data) {
+ uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
+ p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
+ p010_image.chroma_stride = p010_image.luma_stride;
+ }
+ jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
+ if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
+ if (!yuv420_image.chroma_data) {
+ uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
+ yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
+ yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
+ }
- jpegr_uncompressed_struct map;
- JPEGR_CHECK(generateGainMap(
- uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
+ // gain map
+ ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
+ jpegr_uncompressed_struct gainmap_image;
+ JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
std::unique_ptr<uint8_t[]> map_data;
- map_data.reset(reinterpret_cast<uint8_t*>(map.data));
-
- JpegEncoderHelper jpeg_encoder_gainmap;
- JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap));
- jpegr_compressed_struct compressed_map;
- compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize();
- compressed_map.length = compressed_map.maxLength;
- compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
- compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-
- sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
- uncompressed_yuv_420_image->colorGamut);
-
- // Convert to Bt601 YUV encoding for JPEG encode; make a copy so as to no clobber client data
- unique_ptr<uint8_t[]> yuv_420_bt601_data = make_unique<uint8_t[]>(
- uncompressed_yuv_420_image->width * uncompressed_yuv_420_image->height * 3 / 2);
- memcpy(yuv_420_bt601_data.get(), uncompressed_yuv_420_image->data,
- uncompressed_yuv_420_image->width * uncompressed_yuv_420_image->height * 3 / 2);
-
- jpegr_uncompressed_struct yuv_420_bt601_image = {
- yuv_420_bt601_data.get(), uncompressed_yuv_420_image->width, uncompressed_yuv_420_image->height,
- uncompressed_yuv_420_image->colorGamut };
- JPEGR_CHECK(convertYuv(&yuv_420_bt601_image, yuv_420_bt601_image.colorGamut,
- ULTRAHDR_COLORGAMUT_P3));
-
- JpegEncoderHelper jpeg_encoder;
- if (!jpeg_encoder.compressImage(yuv_420_bt601_image.data,
- yuv_420_bt601_image.width,
- yuv_420_bt601_image.height, quality,
- icc->getData(), icc->getLength())) {
+ map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
+
+ // compress gain map
+ JpegEncoderHelper jpeg_enc_obj_gm;
+ JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
+ jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
+ .length = static_cast<int>(
+ jpeg_enc_obj_gm.getCompressedImageSize()),
+ .maxLength = static_cast<int>(
+ jpeg_enc_obj_gm.getCompressedImageSize()),
+ .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
+
+ sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut);
+
+ jpegr_uncompressed_struct yuv420_bt601_image = yuv420_image;
+ unique_ptr<uint8_t[]> yuv_420_bt601_data;
+ // Convert to bt601 YUV encoding for JPEG encode
+ if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) {
+ const int yuv_420_bt601_luma_stride = ALIGNM(yuv420_image.width, kJpegBlock);
+ yuv_420_bt601_data =
+ make_unique<uint8_t[]>(yuv_420_bt601_luma_stride * yuv420_image.height * 3 / 2);
+ yuv420_bt601_image.data = yuv_420_bt601_data.get();
+ yuv420_bt601_image.colorGamut = yuv420_image.colorGamut;
+ yuv420_bt601_image.luma_stride = yuv_420_bt601_luma_stride;
+ uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
+ yuv420_bt601_image.chroma_data = data + yuv_420_bt601_luma_stride * yuv420_image.height;
+ yuv420_bt601_image.chroma_stride = yuv_420_bt601_luma_stride >> 1;
+
+ {
+ // copy luma
+ uint8_t* y_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
+ uint8_t* y_src = reinterpret_cast<uint8_t*>(yuv420_image.data);
+ if (yuv420_bt601_image.luma_stride == yuv420_image.luma_stride) {
+ memcpy(y_dst, y_src, yuv420_bt601_image.luma_stride * yuv420_image.height);
+ } else {
+ for (size_t i = 0; i < yuv420_image.height; i++) {
+ memcpy(y_dst, y_src, yuv420_image.width);
+ if (yuv420_image.width != yuv420_bt601_image.luma_stride) {
+ memset(y_dst + yuv420_image.width, 0,
+ yuv420_bt601_image.luma_stride - yuv420_image.width);
+ }
+ y_dst += yuv420_bt601_image.luma_stride;
+ y_src += yuv420_image.luma_stride;
+ }
+ }
+ }
+
+ if (yuv420_bt601_image.chroma_stride == yuv420_image.chroma_stride) {
+ // copy luma
+ uint8_t* ch_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
+ uint8_t* ch_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
+ memcpy(ch_dst, ch_src, yuv420_bt601_image.chroma_stride * yuv420_image.height);
+ } else {
+ // copy cb & cr
+ uint8_t* cb_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
+ uint8_t* cb_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
+ uint8_t* cr_dst = cb_dst + (yuv420_bt601_image.chroma_stride * yuv420_bt601_image.height / 2);
+ uint8_t* cr_src = cb_src + (yuv420_image.chroma_stride * yuv420_image.height / 2);
+ for (size_t i = 0; i < yuv420_image.height / 2; i++) {
+ memcpy(cb_dst, cb_src, yuv420_image.width / 2);
+ memcpy(cr_dst, cr_src, yuv420_image.width / 2);
+ if (yuv420_bt601_image.width / 2 != yuv420_bt601_image.chroma_stride) {
+ memset(cb_dst + yuv420_image.width / 2, 0,
+ yuv420_bt601_image.chroma_stride - yuv420_image.width / 2);
+ memset(cr_dst + yuv420_image.width / 2, 0,
+ yuv420_bt601_image.chroma_stride - yuv420_image.width / 2);
+ }
+ cb_dst += yuv420_bt601_image.chroma_stride;
+ cb_src += yuv420_image.chroma_stride;
+ cr_dst += yuv420_bt601_image.chroma_stride;
+ cr_src += yuv420_image.chroma_stride;
+ }
+ }
+ JPEGR_CHECK(convertYuv(&yuv420_bt601_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
+ }
+
+ // compress 420 image
+ JpegEncoderHelper jpeg_enc_obj_yuv420;
+ if (!jpeg_enc_obj_yuv420.compressImage(reinterpret_cast<uint8_t*>(yuv420_bt601_image.data),
+ reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data),
+ yuv420_bt601_image.width, yuv420_bt601_image.height,
+ yuv420_bt601_image.luma_stride,
+ yuv420_bt601_image.chroma_stride, quality, icc->getData(),
+ icc->getLength())) {
return ERROR_JPEGR_ENCODE_ERROR;
}
- jpegr_compressed_struct jpeg;
- jpeg.data = jpeg_encoder.getCompressedImagePtr();
- jpeg.length = jpeg_encoder.getCompressedImageSize();
- // No ICC since jpeg encode already did it
+ jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(),
+ .length = static_cast<int>(
+ jpeg_enc_obj_yuv420.getCompressedImageSize()),
+ .maxLength = static_cast<int>(
+ jpeg_enc_obj_yuv420.getCompressedImageSize()),
+ .colorGamut = yuv420_image.colorGamut};
+
+ // append gain map, no ICC since JPEG encode already did it
JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
&metadata, dest));
-
return NO_ERROR;
}
/* Encode API-2 */
-status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
- jr_uncompressed_ptr uncompressed_yuv_420_image,
- jr_compressed_ptr compressed_jpeg_image,
- ultrahdr_transfer_function hdr_tf,
- jr_compressed_ptr dest) {
- if (uncompressed_yuv_420_image == nullptr) {
+status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
+ jr_uncompressed_ptr yuv420_image_ptr,
+ jr_compressed_ptr yuv420jpg_image_ptr,
+ ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
+ // validate input arguments
+ if (yuv420_image_ptr == nullptr) {
ALOGE("received nullptr for uncompressed 420 image");
return ERROR_JPEGR_INVALID_NULL_PTR;
}
-
- if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) {
+ if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
ALOGE("received nullptr for compressed jpeg image");
return ERROR_JPEGR_INVALID_NULL_PTR;
}
-
- if (status_t ret = areInputArgumentsValid(
- uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf, dest) != NO_ERROR) {
+ if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest);
+ ret != NO_ERROR) {
return ret;
}
- ultrahdr_metadata_struct metadata;
- metadata.version = kJpegrVersion;
-
- jpegr_uncompressed_struct map;
- JPEGR_CHECK(generateGainMap(
- uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
- std::unique_ptr<uint8_t[]> map_data;
- map_data.reset(reinterpret_cast<uint8_t*>(map.data));
-
- JpegEncoderHelper jpeg_encoder_gainmap;
- JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap));
- jpegr_compressed_struct compressed_map;
- compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize();
- compressed_map.length = compressed_map.maxLength;
- compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
- compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-
- // We just want to check if ICC is present, so don't do a full decode. Note,
- // this doesn't verify that the ICC is valid.
- JpegDecoderHelper decoder;
- std::vector<uint8_t> icc;
- decoder.getCompressedImageParameters(compressed_jpeg_image->data, compressed_jpeg_image->length,
- /* pWidth */ nullptr, /* pHeight */ nullptr,
- &icc, /* exifData */ nullptr);
-
- // Add ICC if not already present.
- if (icc.size() > 0) {
- JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr,
- /* icc */ nullptr, /* icc size */ 0, &metadata, dest));
- } else {
- sp<DataStruct> newIcc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
- uncompressed_yuv_420_image->colorGamut);
- JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr,
- newIcc->getData(), newIcc->getLength(), &metadata, dest));
+ // clean up input structure for later usage
+ jpegr_uncompressed_struct p010_image = *p010_image_ptr;
+ if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
+ if (!p010_image.chroma_data) {
+ uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
+ p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
+ p010_image.chroma_stride = p010_image.luma_stride;
+ }
+ jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
+ if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
+ if (!yuv420_image.chroma_data) {
+ uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
+ yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
+ yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
}
- return NO_ERROR;
+ // gain map
+ ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
+ jpegr_uncompressed_struct gainmap_image;
+ JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
+ std::unique_ptr<uint8_t[]> map_data;
+ map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
+
+ // compress gain map
+ JpegEncoderHelper jpeg_enc_obj_gm;
+ JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
+ jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
+ .length = static_cast<int>(
+ jpeg_enc_obj_gm.getCompressedImageSize()),
+ .maxLength = static_cast<int>(
+ jpeg_enc_obj_gm.getCompressedImageSize()),
+ .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
+
+ return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
}
/* Encode API-3 */
-status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
- jr_compressed_ptr compressed_jpeg_image,
- ultrahdr_transfer_function hdr_tf,
- jr_compressed_ptr dest) {
- if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) {
+status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
+ jr_compressed_ptr yuv420jpg_image_ptr,
+ ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
+ // validate input arguments
+ if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
ALOGE("received nullptr for compressed jpeg image");
return ERROR_JPEGR_INVALID_NULL_PTR;
}
-
- if (status_t ret = areInputArgumentsValid(
- uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr,
- hdr_tf, dest) != NO_ERROR) {
+ if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest); ret != NO_ERROR) {
return ret;
}
- // Note: output is Bt.601 YUV encoded regardless of gamut, due to jpeg decode.
- JpegDecoderHelper jpeg_decoder;
- if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
+ // clean up input structure for later usage
+ jpegr_uncompressed_struct p010_image = *p010_image_ptr;
+ if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
+ if (!p010_image.chroma_data) {
+ uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
+ p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
+ p010_image.chroma_stride = p010_image.luma_stride;
+ }
+
+ // decode input jpeg, gamut is going to be bt601.
+ JpegDecoderHelper jpeg_dec_obj_yuv420;
+ if (!jpeg_dec_obj_yuv420.decompressImage(yuv420jpg_image_ptr->data,
+ yuv420jpg_image_ptr->length)) {
return ERROR_JPEGR_DECODE_ERROR;
}
- jpegr_uncompressed_struct uncompressed_yuv_420_image;
- uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
- uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
- uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
- uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
+ jpegr_uncompressed_struct yuv420_image{};
+ yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
+ yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
+ yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
+ yuv420_image.colorGamut = yuv420jpg_image_ptr->colorGamut;
+ if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
+ if (!yuv420_image.chroma_data) {
+ uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
+ yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
+ yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
+ }
- if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
- || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
+ if (p010_image_ptr->width != yuv420_image.width ||
+ p010_image_ptr->height != yuv420_image.height) {
return ERROR_JPEGR_RESOLUTION_MISMATCH;
}
- ultrahdr_metadata_struct metadata;
- metadata.version = kJpegrVersion;
-
- jpegr_uncompressed_struct map;
- // Indicate that the SDR image is Bt.601 YUV encoded.
- JPEGR_CHECK(generateGainMap(
- &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map,
- true /* sdr_is_601 */ ));
+ // gain map
+ ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
+ jpegr_uncompressed_struct gainmap_image;
+ JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image,
+ true /* sdr_is_601 */));
std::unique_ptr<uint8_t[]> map_data;
- map_data.reset(reinterpret_cast<uint8_t*>(map.data));
-
- JpegEncoderHelper jpeg_encoder_gainmap;
- JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap));
- jpegr_compressed_struct compressed_map;
- compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize();
- compressed_map.length = compressed_map.maxLength;
- compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
- compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-
- // We just want to check if ICC is present, so don't do a full decode. Note,
- // this doesn't verify that the ICC is valid.
- JpegDecoderHelper decoder;
- std::vector<uint8_t> icc;
- decoder.getCompressedImageParameters(compressed_jpeg_image->data, compressed_jpeg_image->length,
- /* pWidth */ nullptr, /* pHeight */ nullptr,
- &icc, /* exifData */ nullptr);
-
- // Add ICC if not already present.
- if (icc.size() > 0) {
- JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr,
- /* icc */ nullptr, /* icc size */ 0, &metadata, dest));
- } else {
- sp<DataStruct> newIcc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
- uncompressed_yuv_420_image.colorGamut);
- JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr,
- newIcc->getData(), newIcc->getLength(), &metadata, dest));
- }
-
- return NO_ERROR;
+ map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
+
+ // compress gain map
+ JpegEncoderHelper jpeg_enc_obj_gm;
+ JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
+ jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
+ .length = static_cast<int>(
+ jpeg_enc_obj_gm.getCompressedImageSize()),
+ .maxLength = static_cast<int>(
+ jpeg_enc_obj_gm.getCompressedImageSize()),
+ .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
+
+ return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
}
/* Encode API-4 */
-status_t JpegR::encodeJPEGR(jr_compressed_ptr compressed_jpeg_image,
- jr_compressed_ptr compressed_gainmap,
- ultrahdr_metadata_ptr metadata,
+status_t JpegR::encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr,
+ jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata,
jr_compressed_ptr dest) {
- if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) {
+ if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
ALOGE("received nullptr for compressed jpeg image");
return ERROR_JPEGR_INVALID_NULL_PTR;
}
-
- if (compressed_gainmap == nullptr || compressed_gainmap->data == nullptr) {
+ if (gainmapjpg_image_ptr == nullptr || gainmapjpg_image_ptr->data == nullptr) {
ALOGE("received nullptr for compressed gain map");
return ERROR_JPEGR_INVALID_NULL_PTR;
}
-
if (dest == nullptr || dest->data == nullptr) {
ALOGE("received nullptr for destination");
return ERROR_JPEGR_INVALID_NULL_PTR;
@@ -510,46 +553,46 @@ status_t JpegR::encodeJPEGR(jr_compressed_ptr compressed_jpeg_image,
// this doesn't verify that the ICC is valid.
JpegDecoderHelper decoder;
std::vector<uint8_t> icc;
- decoder.getCompressedImageParameters(compressed_jpeg_image->data, compressed_jpeg_image->length,
- /* pWidth */ nullptr, /* pHeight */ nullptr,
- &icc, /* exifData */ nullptr);
+ decoder.getCompressedImageParameters(yuv420jpg_image_ptr->data, yuv420jpg_image_ptr->length,
+ /* pWidth */ nullptr, /* pHeight */ nullptr, &icc,
+ /* exifData */ nullptr);
// Add ICC if not already present.
if (icc.size() > 0) {
- JPEGR_CHECK(appendGainMap(compressed_jpeg_image, compressed_gainmap, /* exif */ nullptr,
- /* icc */ nullptr, /* icc size */ 0, metadata, dest));
+ JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
+ /* icc */ nullptr, /* icc size */ 0, metadata, dest));
} else {
- sp<DataStruct> newIcc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
- compressed_jpeg_image->colorGamut);
- JPEGR_CHECK(appendGainMap(compressed_jpeg_image, compressed_gainmap, /* exif */ nullptr,
- newIcc->getData(), newIcc->getLength(), metadata, dest));
+ sp<DataStruct> newIcc =
+ IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420jpg_image_ptr->colorGamut);
+ JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
+ newIcc->getData(), newIcc->getLength(), metadata, dest));
}
return NO_ERROR;
}
-status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_ptr jpegr_info) {
- if (compressed_jpegr_image == nullptr || compressed_jpegr_image->data == nullptr) {
+status_t JpegR::getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr) {
+ if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
ALOGE("received nullptr for compressed jpegr image");
return ERROR_JPEGR_INVALID_NULL_PTR;
}
-
- if (jpegr_info == nullptr) {
+ if (jpeg_image_info_ptr == nullptr) {
ALOGE("received nullptr for compressed jpegr info struct");
return ERROR_JPEGR_INVALID_NULL_PTR;
}
jpegr_compressed_struct primary_image, gainmap_image;
- status_t status =
- extractPrimaryImageAndGainMap(compressed_jpegr_image, &primary_image, &gainmap_image);
+ status_t status = extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_image, &gainmap_image);
if (status != NO_ERROR && status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
return status;
}
- JpegDecoderHelper jpeg_decoder;
- if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
- &jpegr_info->width, &jpegr_info->height,
- jpegr_info->iccData, jpegr_info->exifData)) {
+ JpegDecoderHelper jpeg_dec_obj_hdr;
+ if (!jpeg_dec_obj_hdr.getCompressedImageParameters(primary_image.data, primary_image.length,
+ &jpeg_image_info_ptr->width,
+ &jpeg_image_info_ptr->height,
+ jpeg_image_info_ptr->iccData,
+ jpeg_image_info_ptr->exifData)) {
return ERROR_JPEGR_DECODE_ERROR;
}
@@ -557,41 +600,34 @@ status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_p
}
/* Decode API */
-status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
- jr_uncompressed_ptr dest,
- float max_display_boost,
- jr_exif_ptr exif,
+status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest,
+ float max_display_boost, jr_exif_ptr exif,
ultrahdr_output_format output_format,
- jr_uncompressed_ptr gain_map,
- ultrahdr_metadata_ptr metadata) {
- if (compressed_jpegr_image == nullptr || compressed_jpegr_image->data == nullptr) {
+ jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata) {
+ if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
ALOGE("received nullptr for compressed jpegr image");
return ERROR_JPEGR_INVALID_NULL_PTR;
}
-
if (dest == nullptr || dest->data == nullptr) {
ALOGE("received nullptr for dest image");
return ERROR_JPEGR_INVALID_NULL_PTR;
}
-
if (max_display_boost < 1.0f) {
ALOGE("received bad value for max_display_boost %f", max_display_boost);
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
-
if (exif != nullptr && exif->data == nullptr) {
ALOGE("received nullptr address for exif data");
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
-
if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) {
ALOGE("received bad value for output format %d", output_format);
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
- jpegr_compressed_struct primary_image, gainmap_image;
+ jpegr_compressed_struct primary_jpeg_image, gainmap_jpeg_image;
status_t status =
- extractPrimaryImageAndGainMap(compressed_jpegr_image, &primary_image, &gainmap_image);
+ extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_jpeg_image, &gainmap_jpeg_image);
if (status != NO_ERROR) {
if (output_format != ULTRAHDR_OUTPUT_SDR || status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
ALOGE("received invalid compressed jpegr image");
@@ -599,22 +635,22 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
}
}
- JpegDecoderHelper jpeg_decoder;
- if (!jpeg_decoder.decompressImage(primary_image.data, primary_image.length,
- (output_format == ULTRAHDR_OUTPUT_SDR))) {
+ JpegDecoderHelper jpeg_dec_obj_yuv420;
+ if (!jpeg_dec_obj_yuv420.decompressImage(primary_jpeg_image.data, primary_jpeg_image.length,
+ (output_format == ULTRAHDR_OUTPUT_SDR))) {
return ERROR_JPEGR_DECODE_ERROR;
}
if (output_format == ULTRAHDR_OUTPUT_SDR) {
- if ((jpeg_decoder.getDecompressedImageWidth() *
- jpeg_decoder.getDecompressedImageHeight() * 4) >
- jpeg_decoder.getDecompressedImageSize()) {
+ if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
+ jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 4) >
+ jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
return ERROR_JPEGR_CALCULATION_ERROR;
}
} else {
- if ((jpeg_decoder.getDecompressedImageWidth() *
- jpeg_decoder.getDecompressedImageHeight() * 3 / 2) >
- jpeg_decoder.getDecompressedImageSize()) {
+ if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
+ jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 3 / 2) >
+ jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
return ERROR_JPEGR_CALCULATION_ERROR;
}
}
@@ -623,46 +659,46 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
if (exif->data == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
}
- if (exif->length < jpeg_decoder.getEXIFSize()) {
+ if (exif->length < jpeg_dec_obj_yuv420.getEXIFSize()) {
return ERROR_JPEGR_BUFFER_TOO_SMALL;
}
- memcpy(exif->data, jpeg_decoder.getEXIFPtr(), jpeg_decoder.getEXIFSize());
- exif->length = jpeg_decoder.getEXIFSize();
+ memcpy(exif->data, jpeg_dec_obj_yuv420.getEXIFPtr(), jpeg_dec_obj_yuv420.getEXIFSize());
+ exif->length = jpeg_dec_obj_yuv420.getEXIFSize();
}
if (output_format == ULTRAHDR_OUTPUT_SDR) {
- dest->width = jpeg_decoder.getDecompressedImageWidth();
- dest->height = jpeg_decoder.getDecompressedImageHeight();
- memcpy(dest->data, jpeg_decoder.getDecompressedImagePtr(), dest->width * dest->height * 4);
+ dest->width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
+ dest->height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
+ memcpy(dest->data, jpeg_dec_obj_yuv420.getDecompressedImagePtr(),
+ dest->width * dest->height * 4);
return NO_ERROR;
}
- JpegDecoderHelper gain_map_decoder;
- if (!gain_map_decoder.decompressImage(gainmap_image.data, gainmap_image.length)) {
+ JpegDecoderHelper jpeg_dec_obj_gm;
+ if (!jpeg_dec_obj_gm.decompressImage(gainmap_jpeg_image.data, gainmap_jpeg_image.length)) {
return ERROR_JPEGR_DECODE_ERROR;
}
- if ((gain_map_decoder.getDecompressedImageWidth() *
- gain_map_decoder.getDecompressedImageHeight()) >
- gain_map_decoder.getDecompressedImageSize()) {
+ if ((jpeg_dec_obj_gm.getDecompressedImageWidth() * jpeg_dec_obj_gm.getDecompressedImageHeight()) >
+ jpeg_dec_obj_gm.getDecompressedImageSize()) {
return ERROR_JPEGR_CALCULATION_ERROR;
}
- jpegr_uncompressed_struct map;
- map.data = gain_map_decoder.getDecompressedImagePtr();
- map.width = gain_map_decoder.getDecompressedImageWidth();
- map.height = gain_map_decoder.getDecompressedImageHeight();
+ jpegr_uncompressed_struct gainmap_image;
+ gainmap_image.data = jpeg_dec_obj_gm.getDecompressedImagePtr();
+ gainmap_image.width = jpeg_dec_obj_gm.getDecompressedImageWidth();
+ gainmap_image.height = jpeg_dec_obj_gm.getDecompressedImageHeight();
- if (gain_map != nullptr) {
- gain_map->width = map.width;
- gain_map->height = map.height;
- int size = gain_map->width * gain_map->height;
- gain_map->data = malloc(size);
- memcpy(gain_map->data, map.data, size);
+ if (gainmap_image_ptr != nullptr) {
+ gainmap_image_ptr->width = gainmap_image.width;
+ gainmap_image_ptr->height = gainmap_image.height;
+ int size = gainmap_image_ptr->width * gainmap_image_ptr->height;
+ gainmap_image_ptr->data = malloc(size);
+ memcpy(gainmap_image_ptr->data, gainmap_image.data, size);
}
ultrahdr_metadata_struct uhdr_metadata;
- if (!getMetadataFromXMP(static_cast<uint8_t*>(gain_map_decoder.getXMPPtr()),
- gain_map_decoder.getXMPSize(), &uhdr_metadata)) {
+ if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_dec_obj_gm.getXMPPtr()),
+ jpeg_dec_obj_gm.getXMPSize(), &uhdr_metadata)) {
return ERROR_JPEGR_INVALID_METADATA;
}
@@ -677,32 +713,33 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
metadata->hdrCapacityMax = uhdr_metadata.hdrCapacityMax;
}
- jpegr_uncompressed_struct uncompressed_yuv_420_image;
- uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
- uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
- uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
- uncompressed_yuv_420_image.colorGamut = IccHelper::readIccColorGamut(
- jpeg_decoder.getICCPtr(), jpeg_decoder.getICCSize());
+ jpegr_uncompressed_struct yuv420_image;
+ yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
+ yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
+ yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
+ yuv420_image.colorGamut = IccHelper::readIccColorGamut(jpeg_dec_obj_yuv420.getICCPtr(),
+ jpeg_dec_obj_yuv420.getICCSize());
+ yuv420_image.luma_stride = yuv420_image.width;
+ uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
+ yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
+ yuv420_image.chroma_stride = yuv420_image.width >> 1;
- JPEGR_CHECK(applyGainMap(&uncompressed_yuv_420_image, &map, &uhdr_metadata, output_format,
+ JPEGR_CHECK(applyGainMap(&yuv420_image, &gainmap_image, &uhdr_metadata, output_format,
max_display_boost, dest));
return NO_ERROR;
}
-status_t JpegR::compressGainMap(jr_uncompressed_ptr uncompressed_gain_map,
- JpegEncoderHelper* jpeg_encoder) {
- if (uncompressed_gain_map == nullptr || jpeg_encoder == nullptr) {
+status_t JpegR::compressGainMap(jr_uncompressed_ptr gainmap_image_ptr,
+ JpegEncoderHelper* jpeg_enc_obj_ptr) {
+ if (gainmap_image_ptr == nullptr || jpeg_enc_obj_ptr == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
}
// Don't need to convert YUV to Bt601 since single channel
- if (!jpeg_encoder->compressImage(uncompressed_gain_map->data,
- uncompressed_gain_map->width,
- uncompressed_gain_map->height,
- kMapCompressQuality,
- nullptr,
- 0,
- true /* isSingleChannel */)) {
+ if (!jpeg_enc_obj_ptr->compressImage(reinterpret_cast<uint8_t*>(gainmap_image_ptr->data), nullptr,
+ gainmap_image_ptr->width, gainmap_image_ptr->height,
+ gainmap_image_ptr->luma_stride, 0, kMapCompressQuality,
+ nullptr, 0)) {
return ERROR_JPEGR_ENCODE_ERROR;
}
@@ -714,13 +751,13 @@ static_assert(kJobSzInRows > 0 && kJobSzInRows % kMapDimensionScaleFactor == 0,
"align job size to kMapDimensionScaleFactor");
class JobQueue {
- public:
+public:
bool dequeueJob(size_t& rowStart, size_t& rowEnd);
void enqueueJob(size_t rowStart, size_t rowEnd);
void markQueueForEnd();
void reset();
- private:
+private:
bool mQueuedAllJobs = false;
std::deque<std::tuple<size_t, size_t>> mJobs;
std::mutex mMutex;
@@ -767,49 +804,48 @@ void JobQueue::reset() {
mQueuedAllJobs = false;
}
-status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
- jr_uncompressed_ptr uncompressed_p010_image,
- ultrahdr_transfer_function hdr_tf,
- ultrahdr_metadata_ptr metadata,
- jr_uncompressed_ptr dest,
- bool sdr_is_601) {
- if (uncompressed_yuv_420_image == nullptr
- || uncompressed_p010_image == nullptr
- || metadata == nullptr
- || dest == nullptr) {
+status_t JpegR::generateGainMap(jr_uncompressed_ptr yuv420_image_ptr,
+ jr_uncompressed_ptr p010_image_ptr,
+ ultrahdr_transfer_function hdr_tf, ultrahdr_metadata_ptr metadata,
+ jr_uncompressed_ptr dest, bool sdr_is_601) {
+ if (yuv420_image_ptr == nullptr || p010_image_ptr == nullptr || metadata == nullptr ||
+ dest == nullptr || yuv420_image_ptr->data == nullptr ||
+ yuv420_image_ptr->chroma_data == nullptr || p010_image_ptr->data == nullptr ||
+ p010_image_ptr->chroma_data == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
}
-
- if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width
- || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) {
+ if (yuv420_image_ptr->width != p010_image_ptr->width ||
+ yuv420_image_ptr->height != p010_image_ptr->height) {
return ERROR_JPEGR_RESOLUTION_MISMATCH;
}
-
- if (uncompressed_yuv_420_image->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED
- || uncompressed_p010_image->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
+ if (yuv420_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
+ p010_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
return ERROR_JPEGR_INVALID_COLORGAMUT;
}
- size_t image_width = uncompressed_yuv_420_image->width;
- size_t image_height = uncompressed_yuv_420_image->height;
+ size_t image_width = yuv420_image_ptr->width;
+ size_t image_height = yuv420_image_ptr->height;
size_t map_width = image_width / kMapDimensionScaleFactor;
size_t map_height = image_height / kMapDimensionScaleFactor;
- size_t map_stride = static_cast<size_t>(
- floor((map_width + kJpegBlock - 1) / kJpegBlock)) * kJpegBlock;
- size_t map_height_aligned = ((map_height + 1) >> 1) << 1;
- dest->width = map_stride;
- dest->height = map_height_aligned;
+ dest->data = new uint8_t[map_width * map_height];
+ dest->width = map_width;
+ dest->height = map_height;
dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
- dest->data = new uint8_t[map_stride * map_height_aligned];
+ dest->luma_stride = map_width;
+ dest->chroma_data = nullptr;
+ dest->chroma_stride = 0;
std::unique_ptr<uint8_t[]> map_data;
map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
ColorTransformFn hdrInvOetf = nullptr;
- float hdr_white_nits = kSdrWhiteNits;
+ float hdr_white_nits;
switch (hdr_tf) {
case ULTRAHDR_TF_LINEAR:
hdrInvOetf = identityConversion;
+ // Note: this will produce clipping if the input exceeds kHlgMaxNits.
+ // TODO: TF LINEAR will be deprecated.
+ hdr_white_nits = kHlgMaxNits;
break;
case ULTRAHDR_TF_HLG:
#if USE_HLG_INVOETF_LUT
@@ -843,12 +879,12 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
float log2MinBoost = log2(metadata->minContentBoost);
float log2MaxBoost = log2(metadata->maxContentBoost);
- ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
- uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
+ ColorTransformFn hdrGamutConversionFn =
+ getHdrConversionFn(yuv420_image_ptr->colorGamut, p010_image_ptr->colorGamut);
ColorCalculationFn luminanceFn = nullptr;
ColorTransformFn sdrYuvToRgbFn = nullptr;
- switch (uncompressed_yuv_420_image->colorGamut) {
+ switch (yuv420_image_ptr->colorGamut) {
case ULTRAHDR_COLORGAMUT_BT709:
luminanceFn = srgbLuminance;
sdrYuvToRgbFn = srgbYuvToRgb;
@@ -870,7 +906,7 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
}
ColorTransformFn hdrYuvToRgbFn = nullptr;
- switch (uncompressed_p010_image->colorGamut) {
+ switch (p010_image_ptr->colorGamut) {
case ULTRAHDR_COLORGAMUT_BT709:
hdrYuvToRgbFn = srgbYuvToRgb;
break;
@@ -890,18 +926,15 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
JobQueue jobQueue;
- std::function<void()> generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image,
- metadata, dest, hdrInvOetf, hdrGamutConversionFn,
- luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn, hdr_white_nits,
- log2MinBoost, log2MaxBoost, &jobQueue]() -> void {
+ std::function<void()> generateMap = [yuv420_image_ptr, p010_image_ptr, metadata, dest, hdrInvOetf,
+ hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn,
+ hdrYuvToRgbFn, hdr_white_nits, log2MinBoost, log2MaxBoost,
+ &jobQueue]() -> void {
size_t rowStart, rowEnd;
- size_t dest_map_width = uncompressed_yuv_420_image->width / kMapDimensionScaleFactor;
- size_t dest_map_stride = dest->width;
while (jobQueue.dequeueJob(rowStart, rowEnd)) {
for (size_t y = rowStart; y < rowEnd; ++y) {
- for (size_t x = 0; x < dest_map_width; ++x) {
- Color sdr_yuv_gamma =
- sampleYuv420(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y);
+ for (size_t x = 0; x < dest->width; ++x) {
+ Color sdr_yuv_gamma = sampleYuv420(yuv420_image_ptr, kMapDimensionScaleFactor, x, y);
Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
// We are assuming the SDR input is always sRGB transfer.
#if USE_SRGB_INVOETF_LUT
@@ -911,15 +944,15 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
#endif
float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
- Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
+ Color hdr_yuv_gamma = sampleP010(p010_image_ptr, kMapDimensionScaleFactor, x, y);
Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
hdr_rgb = hdrGamutConversionFn(hdr_rgb);
float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
- size_t pixel_idx = x + y * dest_map_stride;
+ size_t pixel_idx = x + y * dest->width;
reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
- encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
+ encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
}
}
}
@@ -945,71 +978,64 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
return NO_ERROR;
}
-status_t JpegR::applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
- jr_uncompressed_ptr uncompressed_gain_map,
- ultrahdr_metadata_ptr metadata,
- ultrahdr_output_format output_format,
- float max_display_boost,
+status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr,
+ jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata,
+ ultrahdr_output_format output_format, float max_display_boost,
jr_uncompressed_ptr dest) {
- if (uncompressed_yuv_420_image == nullptr
- || uncompressed_gain_map == nullptr
- || metadata == nullptr
- || dest == nullptr) {
+ if (yuv420_image_ptr == nullptr || gainmap_image_ptr == nullptr || metadata == nullptr ||
+ dest == nullptr || yuv420_image_ptr->data == nullptr ||
+ yuv420_image_ptr->chroma_data == nullptr || gainmap_image_ptr->data == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
}
-
- if (metadata->version.compare("1.0")) {
- ALOGE("Unsupported metadata version: %s", metadata->version.c_str());
- return ERROR_JPEGR_UNSUPPORTED_METADATA;
+ if (metadata->version.compare(kJpegrVersion)) {
+ ALOGE("Unsupported metadata version: %s", metadata->version.c_str());
+ return ERROR_JPEGR_UNSUPPORTED_METADATA;
}
if (metadata->gamma != 1.0f) {
- ALOGE("Unsupported metadata gamma: %f", metadata->gamma);
- return ERROR_JPEGR_UNSUPPORTED_METADATA;
+ ALOGE("Unsupported metadata gamma: %f", metadata->gamma);
+ return ERROR_JPEGR_UNSUPPORTED_METADATA;
}
if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) {
- ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr,
- metadata->offsetHdr);
- return ERROR_JPEGR_UNSUPPORTED_METADATA;
+ ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr, metadata->offsetHdr);
+ return ERROR_JPEGR_UNSUPPORTED_METADATA;
}
- if (metadata->hdrCapacityMin != metadata->minContentBoost
- || metadata->hdrCapacityMax != metadata->maxContentBoost) {
- ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin,
- metadata->hdrCapacityMax);
- return ERROR_JPEGR_UNSUPPORTED_METADATA;
+ if (metadata->hdrCapacityMin != metadata->minContentBoost ||
+ metadata->hdrCapacityMax != metadata->maxContentBoost) {
+ ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin,
+ metadata->hdrCapacityMax);
+ return ERROR_JPEGR_UNSUPPORTED_METADATA;
}
// TODO: remove once map scaling factor is computed based on actual map dims
- size_t image_width = uncompressed_yuv_420_image->width;
- size_t image_height = uncompressed_yuv_420_image->height;
+ size_t image_width = yuv420_image_ptr->width;
+ size_t image_height = yuv420_image_ptr->height;
size_t map_width = image_width / kMapDimensionScaleFactor;
size_t map_height = image_height / kMapDimensionScaleFactor;
- map_width = static_cast<size_t>(
- floor((map_width + kJpegBlock - 1) / kJpegBlock)) * kJpegBlock;
- map_height = ((map_height + 1) >> 1) << 1;
- if (map_width != uncompressed_gain_map->width
- || map_height != uncompressed_gain_map->height) {
- ALOGE("gain map dimensions and primary image dimensions are not to scale");
+ if (map_width != gainmap_image_ptr->width || map_height != gainmap_image_ptr->height) {
+ ALOGE("gain map dimensions and primary image dimensions are not to scale, computed gain map "
+ "resolution is %dx%d, received gain map resolution is %dx%d",
+ (int)map_width, (int)map_height, gainmap_image_ptr->width, gainmap_image_ptr->height);
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
- dest->width = uncompressed_yuv_420_image->width;
- dest->height = uncompressed_yuv_420_image->height;
+ dest->width = yuv420_image_ptr->width;
+ dest->height = yuv420_image_ptr->height;
ShepardsIDW idwTable(kMapDimensionScaleFactor);
float display_boost = std::min(max_display_boost, metadata->maxContentBoost);
GainLUT gainLUT(metadata, display_boost);
JobQueue jobQueue;
- std::function<void()> applyRecMap = [uncompressed_yuv_420_image, uncompressed_gain_map,
- metadata, dest, &jobQueue, &idwTable, output_format,
- &gainLUT, display_boost]() -> void {
- size_t width = uncompressed_yuv_420_image->width;
- size_t height = uncompressed_yuv_420_image->height;
+ std::function<void()> applyRecMap = [yuv420_image_ptr, gainmap_image_ptr, metadata, dest,
+ &jobQueue, &idwTable, output_format, &gainLUT,
+ display_boost]() -> void {
+ size_t width = yuv420_image_ptr->width;
+ size_t height = yuv420_image_ptr->height;
size_t rowStart, rowEnd;
while (jobQueue.dequeueJob(rowStart, rowEnd)) {
for (size_t y = rowStart; y < rowEnd; ++y) {
for (size_t x = 0; x < width; ++x) {
- Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
+ Color yuv_gamma_sdr = getYuv420Pixel(yuv420_image_ptr, x, y);
// Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients
Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr);
// We are assuming the SDR base image is always sRGB transfer.
@@ -1025,9 +1051,9 @@ status_t JpegR::applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
// Currently map_scale_factor is of type size_t, but it could be changed to a float
// later.
if (map_scale_factor != floorf(map_scale_factor)) {
- gain = sampleMap(uncompressed_gain_map, map_scale_factor, x, y);
+ gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y);
} else {
- gain = sampleMap(uncompressed_gain_map, map_scale_factor, x, y, idwTable);
+ gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y, idwTable);
}
#if USE_APPLY_GAIN_LUT
@@ -1039,14 +1065,12 @@ status_t JpegR::applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
size_t pixel_idx = x + y * width;
switch (output_format) {
- case ULTRAHDR_OUTPUT_HDR_LINEAR:
- {
+ case ULTRAHDR_OUTPUT_HDR_LINEAR: {
uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr);
reinterpret_cast<uint64_t*>(dest->data)[pixel_idx] = rgba_f16;
break;
}
- case ULTRAHDR_OUTPUT_HDR_HLG:
- {
+ case ULTRAHDR_OUTPUT_HDR_HLG: {
#if USE_HLG_OETF_LUT
ColorTransformFn hdrOetf = hlgOetfLUT;
#else
@@ -1057,9 +1081,8 @@ status_t JpegR::applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
break;
}
- case ULTRAHDR_OUTPUT_HDR_PQ:
- {
-#if USE_HLG_OETF_LUT
+ case ULTRAHDR_OUTPUT_HDR_PQ: {
+#if USE_PQ_OETF_LUT
ColorTransformFn hdrOetf = pqOetfLUT;
#else
ColorTransformFn hdrOetf = pqOetf;
@@ -1069,8 +1092,8 @@ status_t JpegR::applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
break;
}
- default:
- {}
+ default: {
+ }
// Should be impossible to hit after input validation.
}
}
@@ -1083,9 +1106,9 @@ status_t JpegR::applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
for (int th = 0; th < threads - 1; th++) {
workers.push_back(std::thread(applyRecMap));
}
- const int rowStep = threads == 1 ? uncompressed_yuv_420_image->height : kJobSzInRows;
- for (int rowStart = 0; rowStart < uncompressed_yuv_420_image->height;) {
- int rowEnd = std::min(rowStart + rowStep, uncompressed_yuv_420_image->height);
+ const int rowStep = threads == 1 ? yuv420_image_ptr->height : kJobSzInRows;
+ for (int rowStart = 0; rowStart < yuv420_image_ptr->height;) {
+ int rowEnd = std::min(rowStart + rowStep, yuv420_image_ptr->height);
jobQueue.enqueueJob(rowStart, rowEnd);
rowStart = rowEnd;
}
@@ -1095,18 +1118,18 @@ status_t JpegR::applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
return NO_ERROR;
}
-status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr compressed_jpegr_image,
- jr_compressed_ptr primary_image,
- jr_compressed_ptr gain_map) {
- if (compressed_jpegr_image == nullptr) {
+status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
+ jr_compressed_ptr primary_jpg_image_ptr,
+ jr_compressed_ptr gainmap_jpg_image_ptr) {
+ if (jpegr_image_ptr == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
}
MessageHandler msg_handler;
std::shared_ptr<DataSegment> seg =
- DataSegment::Create(DataRange(0, compressed_jpegr_image->length),
- static_cast<const uint8_t*>(compressed_jpegr_image->data),
- DataSegment::BufferDispositionPolicy::kDontDelete);
+ DataSegment::Create(DataRange(0, jpegr_image_ptr->length),
+ static_cast<const uint8_t*>(jpegr_image_ptr->data),
+ DataSegment::BufferDispositionPolicy::kDontDelete);
DataSegmentDataSource data_source(seg);
JpegInfoBuilder jpeg_info_builder;
jpeg_info_builder.SetImageLimit(2);
@@ -1125,20 +1148,20 @@ status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr compressed_jpegr
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
- if (primary_image != nullptr) {
- primary_image->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
- image_ranges[0].GetBegin();
- primary_image->length = image_ranges[0].GetLength();
+ if (primary_jpg_image_ptr != nullptr) {
+ primary_jpg_image_ptr->data =
+ static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[0].GetBegin();
+ primary_jpg_image_ptr->length = image_ranges[0].GetLength();
}
if (image_ranges.size() == 1) {
return ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND;
}
- if (gain_map != nullptr) {
- gain_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
- image_ranges[1].GetBegin();
- gain_map->length = image_ranges[1].GetLength();
+ if (gainmap_jpg_image_ptr != nullptr) {
+ gainmap_jpg_image_ptr->data =
+ static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[1].GetBegin();
+ gainmap_jpg_image_ptr->length = image_ranges[1].GetLength();
}
// TODO: choose primary image and gain map image carefully
@@ -1153,7 +1176,8 @@ status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr compressed_jpegr
// JPEG/R structure:
// SOI (ff d8)
//
-// (Optional, only if EXIF package is from outside)
+// (Optional, if EXIF package is from outside (Encode API-0 API-1), or if EXIF package presents
+// in the JPEG input (Encode API-2, API-3, API-4))
// APP1 (ff e1)
// 2 bytes of length (2 + length of exif package)
// EXIF package (this includes the first two bytes representing the package length)
@@ -1167,7 +1191,7 @@ status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr compressed_jpegr
// 2 bytes of length
// MPF
//
-// (Required) primary image (without the first two bytes (SOI), may have other packages)
+// (Required) primary image (without the first two bytes (SOI) and EXIF, may have other packages)
//
// SOI (ff d8)
//
@@ -1183,63 +1207,83 @@ status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr compressed_jpegr
// Exif 2.2 spec for EXIF marker
// Adobe XMP spec part 3 for XMP marker
// ICC v4.3 spec for ICC
-status_t JpegR::appendGainMap(jr_compressed_ptr compressed_jpeg_image,
- jr_compressed_ptr compressed_gain_map,
- jr_exif_ptr exif,
- void* icc, size_t icc_size,
- ultrahdr_metadata_ptr metadata,
+status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
+ jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif,
+ void* pIcc, size_t icc_size, ultrahdr_metadata_ptr metadata,
jr_compressed_ptr dest) {
- if (compressed_jpeg_image == nullptr
- || compressed_gain_map == nullptr
- || metadata == nullptr
- || dest == nullptr) {
+ if (primary_jpg_image_ptr == nullptr || gainmap_jpg_image_ptr == nullptr || metadata == nullptr ||
+ dest == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
}
-
if (metadata->version.compare("1.0")) {
ALOGE("received bad value for version: %s", metadata->version.c_str());
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
if (metadata->maxContentBoost < metadata->minContentBoost) {
ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost,
- metadata->maxContentBoost);
+ metadata->maxContentBoost);
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
-
if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) {
ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin,
- metadata->hdrCapacityMax);
+ metadata->hdrCapacityMax);
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
-
if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) {
- ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr,
- metadata->offsetHdr);
+ ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr, metadata->offsetHdr);
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
-
if (metadata->gamma <= 0.0f) {
ALOGE("received bad value for gamma %f", metadata->gamma);
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
const string nameSpace = "http://ns.adobe.com/xap/1.0/";
- const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
+ const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
// calculate secondary image length first, because the length will be written into the primary
// image xmp
const string xmp_secondary = generateXmpForSecondaryImage(*metadata);
const int xmp_secondary_length = 2 /* 2 bytes representing the length of the package */
- + nameSpaceLength /* 29 bytes length of name space including \0 */
- + xmp_secondary.size(); /* length of xmp packet */
+ + nameSpaceLength /* 29 bytes length of name space including \0 */
+ + xmp_secondary.size(); /* length of xmp packet */
const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */
- + xmp_secondary_length
- + compressed_gain_map->length;
+ + xmp_secondary_length + gainmap_jpg_image_ptr->length;
// primary image
const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata);
// same as primary
const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();
+ // Check if EXIF package presents in the JPEG input.
+ // If so, extract and remove the EXIF package.
+ JpegDecoderHelper decoder;
+ if (!decoder.extractEXIF(primary_jpg_image_ptr->data, primary_jpg_image_ptr->length)) {
+ return ERROR_JPEGR_DECODE_ERROR;
+ }
+ jpegr_exif_struct exif_from_jpg = {.data = nullptr, .length = 0};
+ jpegr_compressed_struct new_jpg_image = {.data = nullptr,
+ .length = 0,
+ .maxLength = 0,
+ .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
+ std::unique_ptr<uint8_t[]> dest_data;
+ if (decoder.getEXIFPos() >= 0) {
+ if (pExif != nullptr) {
+ ALOGE("received EXIF from outside while the primary image already contains EXIF");
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ copyJpegWithoutExif(&new_jpg_image,
+ primary_jpg_image_ptr,
+ decoder.getEXIFPos(),
+ decoder.getEXIFSize());
+ dest_data.reset(reinterpret_cast<uint8_t*>(new_jpg_image.data));
+ exif_from_jpg.data = decoder.getEXIFPtr();
+ exif_from_jpg.length = decoder.getEXIFSize();
+ pExif = &exif_from_jpg;
+ }
+
+ jr_compressed_ptr final_primary_jpg_image_ptr =
+ new_jpg_image.length == 0 ? primary_jpg_image_ptr : &new_jpg_image;
+
int pos = 0;
// Begin primary image
// Write SOI
@@ -1247,15 +1291,15 @@ status_t JpegR::appendGainMap(jr_compressed_ptr compressed_jpeg_image,
JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
// Write EXIF
- if (exif != nullptr) {
- const int length = 2 + exif->length;
+ if (pExif != nullptr) {
+ const int length = 2 + pExif->length;
const uint8_t lengthH = ((length >> 8) & 0xff);
const uint8_t lengthL = (length & 0xff);
JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
- JPEGR_CHECK(Write(dest, exif->data, exif->length, pos));
+ JPEGR_CHECK(Write(dest, pExif->data, pExif->length, pos));
}
// Prepare and write XMP
@@ -1272,42 +1316,40 @@ status_t JpegR::appendGainMap(jr_compressed_ptr compressed_jpeg_image,
}
// Write ICC
- if (icc != nullptr && icc_size > 0) {
- const int length = icc_size + 2;
- const uint8_t lengthH = ((length >> 8) & 0xff);
- const uint8_t lengthL = (length & 0xff);
- JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
- JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
- JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
- JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
- JPEGR_CHECK(Write(dest, icc, icc_size, pos));
+ if (pIcc != nullptr && icc_size > 0) {
+ const int length = icc_size + 2;
+ const uint8_t lengthH = ((length >> 8) & 0xff);
+ const uint8_t lengthL = (length & 0xff);
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
+ JPEGR_CHECK(Write(dest, pIcc, icc_size, pos));
}
// Prepare and write MPF
{
- const int length = 2 + calculateMpfSize();
- const uint8_t lengthH = ((length >> 8) & 0xff);
- const uint8_t lengthL = (length & 0xff);
- int primary_image_size = pos + length + compressed_jpeg_image->length;
- // between APP2 + package size + signature
- // ff e2 00 58 4d 50 46 00
- // 2 + 2 + 4 = 8 (bytes)
- // and ff d8 sign of the secondary image
- int secondary_image_offset = primary_image_size - pos - 8;
- sp<DataStruct> mpf = generateMpf(primary_image_size,
- 0, /* primary_image_offset */
- secondary_image_size,
- secondary_image_offset);
- JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
- JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
- JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
- JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
- JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
+ const int length = 2 + calculateMpfSize();
+ const uint8_t lengthH = ((length >> 8) & 0xff);
+ const uint8_t lengthL = (length & 0xff);
+ int primary_image_size = pos + length + final_primary_jpg_image_ptr->length;
+ // between APP2 + package size + signature
+ // ff e2 00 58 4d 50 46 00
+ // 2 + 2 + 4 = 8 (bytes)
+ // and ff d8 sign of the secondary image
+ int secondary_image_offset = primary_image_size - pos - 8;
+ sp<DataStruct> mpf = generateMpf(primary_image_size, 0, /* primary_image_offset */
+ secondary_image_size, secondary_image_offset);
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
+ JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
}
// Write primary image
- JPEGR_CHECK(Write(dest,
- (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
+ JPEGR_CHECK(Write(dest, (uint8_t*)final_primary_jpg_image_ptr->data + 2,
+ final_primary_jpg_image_ptr->length - 2, pos));
// Finish primary image
// Begin secondary image (gain map)
@@ -1329,8 +1371,8 @@ status_t JpegR::appendGainMap(jr_compressed_ptr compressed_jpeg_image,
}
// Write secondary image
- JPEGR_CHECK(Write(dest,
- (uint8_t*)compressed_gain_map->data + 2, compressed_gain_map->length - 2, pos));
+ JPEGR_CHECK(Write(dest, (uint8_t*)gainmap_jpg_image_ptr->data + 2,
+ gainmap_jpg_image_ptr->length - 2, pos));
// Set back length
dest->length = pos;
@@ -1343,62 +1385,52 @@ status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) {
if (src == nullptr || dest == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
}
-
- uint16_t* src_luma_data = reinterpret_cast<uint16_t*>(src->data);
- size_t src_luma_stride = src->luma_stride == 0 ? src->width : src->luma_stride;
-
- uint16_t* src_chroma_data;
- size_t src_chroma_stride;
- if (src->chroma_data == nullptr) {
- src_chroma_stride = src_luma_stride;
- src_chroma_data = &reinterpret_cast<uint16_t*>(src->data)[src_luma_stride * src->height];
- } else {
- src_chroma_stride = src->chroma_stride;
- src_chroma_data = reinterpret_cast<uint16_t*>(src->chroma_data);
+ if (src->width != dest->width || src->height != dest->height) {
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
- dest->width = src->width;
- dest->height = src->height;
-
- size_t dest_luma_pixel_count = dest->width * dest->height;
-
+ uint16_t* src_y_data = reinterpret_cast<uint16_t*>(src->data);
+ uint8_t* dst_y_data = reinterpret_cast<uint8_t*>(dest->data);
for (size_t y = 0; y < src->height; ++y) {
+ uint16_t* src_y_row = src_y_data + y * src->luma_stride;
+ uint8_t* dst_y_row = dst_y_data + y * dest->luma_stride;
for (size_t x = 0; x < src->width; ++x) {
- size_t src_y_idx = y * src_luma_stride + x;
- size_t src_u_idx = (y >> 1) * src_chroma_stride + (x & ~0x1);
- size_t src_v_idx = src_u_idx + 1;
-
- uint16_t y_uint = src_luma_data[src_y_idx] >> 6;
- uint16_t u_uint = src_chroma_data[src_u_idx] >> 6;
- uint16_t v_uint = src_chroma_data[src_v_idx] >> 6;
-
- size_t dest_y_idx = x + y * dest->width;
- size_t dest_uv_idx = x / 2 + (y / 2) * (dest->width / 2);
-
- uint8_t* y = &reinterpret_cast<uint8_t*>(dest->data)[dest_y_idx];
- uint8_t* u = &reinterpret_cast<uint8_t*>(dest->data)[dest_luma_pixel_count + dest_uv_idx];
- uint8_t* v = &reinterpret_cast<uint8_t*>(
- dest->data)[dest_luma_pixel_count * 5 / 4 + dest_uv_idx];
-
- *y = static_cast<uint8_t>((y_uint >> 2) & 0xff);
- *u = static_cast<uint8_t>((u_uint >> 2) & 0xff);
- *v = static_cast<uint8_t>((v_uint >> 2) & 0xff);
+ uint16_t y_uint = src_y_row[x] >> 6;
+ dst_y_row[x] = static_cast<uint8_t>((y_uint >> 2) & 0xff);
+ }
+ if (dest->width != dest->luma_stride) {
+ memset(dst_y_row + dest->width, 0, dest->luma_stride - dest->width);
+ }
+ }
+ uint16_t* src_uv_data = reinterpret_cast<uint16_t*>(src->chroma_data);
+ uint8_t* dst_u_data = reinterpret_cast<uint8_t*>(dest->chroma_data);
+ size_t dst_v_offset = (dest->chroma_stride * dest->height / 2);
+ uint8_t* dst_v_data = dst_u_data + dst_v_offset;
+ for (size_t y = 0; y < src->height / 2; ++y) {
+ uint16_t* src_uv_row = src_uv_data + y * src->chroma_stride;
+ uint8_t* dst_u_row = dst_u_data + y * dest->chroma_stride;
+ uint8_t* dst_v_row = dst_v_data + y * dest->chroma_stride;
+ for (size_t x = 0; x < src->width / 2; ++x) {
+ uint16_t u_uint = src_uv_row[x << 1] >> 6;
+ uint16_t v_uint = src_uv_row[(x << 1) + 1] >> 6;
+ dst_u_row[x] = static_cast<uint8_t>((u_uint >> 2) & 0xff);
+ dst_v_row[x] = static_cast<uint8_t>((v_uint >> 2) & 0xff);
+ }
+ if (dest->width / 2 != dest->chroma_stride) {
+ memset(dst_u_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
+ memset(dst_v_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
}
}
-
dest->colorGamut = src->colorGamut;
-
return NO_ERROR;
}
-status_t JpegR::convertYuv(jr_uncompressed_ptr image,
- ultrahdr_color_gamut src_encoding,
+status_t JpegR::convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding,
ultrahdr_color_gamut dest_encoding) {
if (image == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
}
-
- if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED
- || dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
+ if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
+ dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
return ERROR_JPEGR_INVALID_COLORGAMUT;
}
diff --git a/libs/ultrahdr/tests/AndroidTest.xml b/libs/ultrahdr/tests/AndroidTest.xml
new file mode 100644
index 0000000000..1754a5ccb9
--- /dev/null
+++ b/libs/ultrahdr/tests/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<configuration description="Unit test configuration for ultrahdr_unit_test">
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push-file" key="ultrahdr_unit_test" value="/data/local/tmp/ultrahdr_unit_test" />
+ <option name="push" value="data/*->/data/local/tmp/" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="ultrahdr_unit_test" />
+ </test>
+</configuration>
diff --git a/libs/ultrahdr/tests/data/raw_p010_image_with_stride.p010 b/libs/ultrahdr/tests/data/raw_p010_image_with_stride.p010
deleted file mode 100644
index e7a5dc84dc..0000000000
--- a/libs/ultrahdr/tests/data/raw_p010_image_with_stride.p010
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/gainmapmath_test.cpp b/libs/ultrahdr/tests/gainmapmath_test.cpp
index af90365e56..7c2d076992 100644
--- a/libs/ultrahdr/tests/gainmapmath_test.cpp
+++ b/libs/ultrahdr/tests/gainmapmath_test.cpp
@@ -120,7 +120,7 @@ public:
0xB0, 0xB1,
0xB2, 0xB3,
};
- return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709 };
+ return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709, pixels + 16, 4, 2 };
}
Color (*Yuv420Colors())[4] {
@@ -153,7 +153,7 @@ public:
0xA0 << 6, 0xB0 << 6, 0xA1 << 6, 0xB1 << 6,
0xA2 << 6, 0xB2 << 6, 0xA3 << 6, 0xB3 << 6,
};
- return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709 };
+ return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709, pixels + 16, 4, 4 };
}
Color (*P010Colors())[4] {
@@ -636,6 +636,9 @@ TEST_F(GainMapMathTest, TransformYuv420) {
memcpy(out_buf.get(), input.data, out_buf_size);
jpegr_uncompressed_struct output = Yuv420Image();
output.data = out_buf.get();
+ output.chroma_data = out_buf.get() + input.width * input.height;
+ output.luma_stride = input.width;
+ output.chroma_stride = input.width / 2;
transformYuv420(&output, 1, 1, transform);
diff --git a/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp b/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp
index e2da01c373..af0d59edc0 100644
--- a/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp
+++ b/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-#include <ultrahdr/jpegdecoderhelper.h>
-#include <ultrahdr/icc.h>
#include <gtest/gtest.h>
+#include <ultrahdr/icc.h>
+#include <ultrahdr/jpegdecoderhelper.h>
#include <utils/Log.h>
#include <fcntl.h>
@@ -24,13 +24,13 @@
namespace android::ultrahdr {
// No ICC or EXIF
-#define YUV_IMAGE "/sdcard/Documents/minnie-320x240-yuv.jpg"
+#define YUV_IMAGE "/data/local/tmp/minnie-320x240-yuv.jpg"
#define YUV_IMAGE_SIZE 20193
// Has ICC and EXIF
-#define YUV_ICC_IMAGE "/sdcard/Documents/minnie-320x240-yuv-icc.jpg"
+#define YUV_ICC_IMAGE "/data/local/tmp/minnie-320x240-yuv-icc.jpg"
#define YUV_ICC_IMAGE_SIZE 34266
// No ICC or EXIF
-#define GREY_IMAGE "/sdcard/Documents/minnie-320x240-y.jpg"
+#define GREY_IMAGE "/data/local/tmp/minnie-320x240-y.jpg"
#define GREY_IMAGE_SIZE 20193
#define IMAGE_WIDTH 320
@@ -44,6 +44,7 @@ public:
};
JpegDecoderHelperTest();
~JpegDecoderHelperTest();
+
protected:
virtual void SetUp();
virtual void TearDown();
@@ -127,8 +128,8 @@ TEST_F(JpegDecoderHelperTest, getCompressedImageParameters) {
std::vector<uint8_t> icc, exif;
JpegDecoderHelper decoder;
- EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvImage.buffer.get(), mYuvImage.size,
- &width, &height, &icc, &exif));
+ EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvImage.buffer.get(), mYuvImage.size, &width,
+ &height, &icc, &exif));
EXPECT_EQ(width, IMAGE_WIDTH);
EXPECT_EQ(height, IMAGE_HEIGHT);
@@ -149,8 +150,7 @@ TEST_F(JpegDecoderHelperTest, getCompressedImageParametersIcc) {
EXPECT_GT(icc.size(), 0);
EXPECT_GT(exif.size(), 0);
- EXPECT_EQ(IccHelper::readIccColorGamut(icc.data(), icc.size()),
- ULTRAHDR_COLORGAMUT_BT709);
+ EXPECT_EQ(IccHelper::readIccColorGamut(icc.data(), icc.size()), ULTRAHDR_COLORGAMUT_BT709);
}
-} // namespace android::ultrahdr
+} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp
index f0e1fa4968..af54eb2a8a 100644
--- a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp
+++ b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp
@@ -22,13 +22,13 @@
namespace android::ultrahdr {
-#define ALIGNED_IMAGE "/sdcard/Documents/minnie-320x240.yu12"
+#define ALIGNED_IMAGE "/data/local/tmp/minnie-320x240.yu12"
#define ALIGNED_IMAGE_WIDTH 320
#define ALIGNED_IMAGE_HEIGHT 240
-#define SINGLE_CHANNEL_IMAGE "/sdcard/Documents/minnie-320x240.y"
+#define SINGLE_CHANNEL_IMAGE "/data/local/tmp/minnie-320x240.y"
#define SINGLE_CHANNEL_IMAGE_WIDTH ALIGNED_IMAGE_WIDTH
#define SINGLE_CHANNEL_IMAGE_HEIGHT ALIGNED_IMAGE_HEIGHT
-#define UNALIGNED_IMAGE "/sdcard/Documents/minnie-318x240.yu12"
+#define UNALIGNED_IMAGE "/data/local/tmp/minnie-318x240.yu12"
#define UNALIGNED_IMAGE_WIDTH 318
#define UNALIGNED_IMAGE_HEIGHT 240
#define JPEG_QUALITY 90
@@ -42,6 +42,7 @@ public:
};
JpegEncoderHelperTest();
~JpegEncoderHelperTest();
+
protected:
virtual void SetUp();
virtual void TearDown();
@@ -103,24 +104,32 @@ void JpegEncoderHelperTest::TearDown() {}
TEST_F(JpegEncoderHelperTest, encodeAlignedImage) {
JpegEncoderHelper encoder;
- EXPECT_TRUE(encoder.compressImage(mAlignedImage.buffer.get(), mAlignedImage.width,
- mAlignedImage.height, JPEG_QUALITY, NULL, 0));
+ EXPECT_TRUE(encoder.compressImage(mAlignedImage.buffer.get(),
+ mAlignedImage.buffer.get() +
+ mAlignedImage.width * mAlignedImage.height,
+ mAlignedImage.width, mAlignedImage.height,
+ mAlignedImage.width, mAlignedImage.width / 2, JPEG_QUALITY,
+ NULL, 0));
ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
}
TEST_F(JpegEncoderHelperTest, encodeUnalignedImage) {
JpegEncoderHelper encoder;
- EXPECT_TRUE(encoder.compressImage(mUnalignedImage.buffer.get(), mUnalignedImage.width,
- mUnalignedImage.height, JPEG_QUALITY, NULL, 0));
+ EXPECT_TRUE(encoder.compressImage(mUnalignedImage.buffer.get(),
+ mUnalignedImage.buffer.get() +
+ mUnalignedImage.width * mUnalignedImage.height,
+ mUnalignedImage.width, mUnalignedImage.height,
+ mUnalignedImage.width, mUnalignedImage.width / 2,
+ JPEG_QUALITY, NULL, 0));
ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
}
TEST_F(JpegEncoderHelperTest, encodeSingleChannelImage) {
JpegEncoderHelper encoder;
- EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), mSingleChannelImage.width,
- mSingleChannelImage.height, JPEG_QUALITY, NULL, 0, true));
+ EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), nullptr,
+ mSingleChannelImage.width, mSingleChannelImage.height,
+ mSingleChannelImage.width, 0, JPEG_QUALITY, NULL, 0));
ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
}
-} // namespace android::ultrahdr
-
+} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/tests/jpegr_test.cpp b/libs/ultrahdr/tests/jpegr_test.cpp
index 41d55ec497..5fa758e88d 100644
--- a/libs/ultrahdr/tests/jpegr_test.cpp
+++ b/libs/ultrahdr/tests/jpegr_test.cpp
@@ -14,810 +14,1232 @@
* limitations under the License.
*/
+#include <sys/time.h>
+#include <fstream>
+#include <iostream>
+
+#include <ultrahdr/gainmapmath.h>
#include <ultrahdr/jpegr.h>
#include <ultrahdr/jpegrutils.h>
-#include <ultrahdr/gainmapmath.h>
-#include <fcntl.h>
-#include <fstream>
+
#include <gtest/gtest.h>
-#include <sys/time.h>
#include <utils/Log.h>
-#define RAW_P010_IMAGE "/sdcard/Documents/raw_p010_image.p010"
-#define RAW_P010_IMAGE_WITH_STRIDE "/sdcard/Documents/raw_p010_image_with_stride.p010"
-#define RAW_YUV420_IMAGE "/sdcard/Documents/raw_yuv420_image.yuv420"
-#define JPEG_IMAGE "/sdcard/Documents/jpeg_image.jpg"
-#define TEST_IMAGE_WIDTH 1280
-#define TEST_IMAGE_HEIGHT 720
-#define TEST_IMAGE_STRIDE 1288
-#define DEFAULT_JPEG_QUALITY 90
-
-#define SAVE_ENCODING_RESULT true
-#define SAVE_DECODING_RESULT true
-#define SAVE_INPUT_RGBA true
+//#define DUMP_OUTPUT
namespace android::ultrahdr {
-struct Timer {
- struct timeval StartingTime;
- struct timeval EndingTime;
- struct timeval ElapsedMicroseconds;
+// resources used by unit tests
+const char* kYCbCrP010FileName = "/data/local/tmp/raw_p010_image.p010";
+const char* kYCbCr420FileName = "/data/local/tmp/raw_yuv420_image.yuv420";
+const char* kSdrJpgFileName = "/data/local/tmp/jpeg_image.jpg";
+const int kImageWidth = 1280;
+const int kImageHeight = 720;
+const int kQuality = 90;
+
+// Wrapper to describe the input type
+typedef enum {
+ YCbCr_p010 = 0,
+ YCbCr_420 = 1,
+} UhdrInputFormat;
+
+/**
+ * Wrapper class for raw resource
+ * Sample usage:
+ * UhdrUnCompressedStructWrapper rawImg(width, height, YCbCr_p010);
+ * rawImg.setImageColorGamut(colorGamut));
+ * rawImg.setImageStride(strideLuma, strideChroma); // optional
+ * rawImg.setChromaMode(false); // optional
+ * rawImg.allocateMemory();
+ * rawImg.loadRawResource(kYCbCrP010FileName);
+ */
+class UhdrUnCompressedStructWrapper {
+public:
+ UhdrUnCompressedStructWrapper(uint32_t width, uint32_t height, UhdrInputFormat format);
+ ~UhdrUnCompressedStructWrapper() = default;
+
+ bool setChromaMode(bool isChromaContiguous);
+ bool setImageStride(int lumaStride, int chromaStride);
+ bool setImageColorGamut(ultrahdr_color_gamut colorGamut);
+ bool allocateMemory();
+ bool loadRawResource(const char* fileName);
+ jr_uncompressed_ptr getImageHandle();
+
+private:
+ std::unique_ptr<uint8_t[]> mLumaData;
+ std::unique_ptr<uint8_t[]> mChromaData;
+ jpegr_uncompressed_struct mImg;
+ UhdrInputFormat mFormat;
+ bool mIsChromaContiguous;
};
-void timerStart(Timer *t) {
- gettimeofday(&t->StartingTime, nullptr);
-}
+/**
+ * Wrapper class for compressed resource
+ * Sample usage:
+ * UhdrCompressedStructWrapper jpgImg(width, height);
+ * rawImg.allocateMemory();
+ */
+class UhdrCompressedStructWrapper {
+public:
+ UhdrCompressedStructWrapper(uint32_t width, uint32_t height);
+ ~UhdrCompressedStructWrapper() = default;
-void timerStop(Timer *t) {
- gettimeofday(&t->EndingTime, nullptr);
-}
+ bool allocateMemory();
+ jr_compressed_ptr getImageHandle();
-int64_t elapsedTime(Timer *t) {
- t->ElapsedMicroseconds.tv_sec = t->EndingTime.tv_sec - t->StartingTime.tv_sec;
- t->ElapsedMicroseconds.tv_usec = t->EndingTime.tv_usec - t->StartingTime.tv_usec;
- return t->ElapsedMicroseconds.tv_sec * 1000000 + t->ElapsedMicroseconds.tv_usec;
-}
+private:
+ std::unique_ptr<uint8_t[]> mData;
+ jpegr_compressed_struct mImg{};
+ uint32_t mWidth;
+ uint32_t mHeight;
+};
-static size_t getFileSize(int fd) {
- struct stat st;
- if (fstat(fd, &st) < 0) {
- ALOGW("%s : fstat failed", __func__);
- return 0;
- }
- return st.st_size; // bytes
+UhdrUnCompressedStructWrapper::UhdrUnCompressedStructWrapper(uint32_t width, uint32_t height,
+ UhdrInputFormat format) {
+ mImg.data = nullptr;
+ mImg.width = width;
+ mImg.height = height;
+ mImg.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ mImg.chroma_data = nullptr;
+ mImg.luma_stride = 0;
+ mImg.chroma_stride = 0;
+ mFormat = format;
+ mIsChromaContiguous = true;
}
-static bool loadFile(const char filename[], void*& result, int* fileLength) {
- int fd = open(filename, O_CLOEXEC);
- if (fd < 0) {
+bool UhdrUnCompressedStructWrapper::setChromaMode(bool isChromaContiguous) {
+ if (mLumaData.get() != nullptr) {
+ std::cerr << "Object has sailed, no further modifications are allowed" << std::endl;
return false;
}
- int length = getFileSize(fd);
- if (length == 0) {
- close(fd);
+ mIsChromaContiguous = isChromaContiguous;
+ return true;
+}
+
+bool UhdrUnCompressedStructWrapper::setImageStride(int lumaStride, int chromaStride) {
+ if (mLumaData.get() != nullptr) {
+ std::cerr << "Object has sailed, no further modifications are allowed" << std::endl;
return false;
}
- if (fileLength != nullptr) {
- *fileLength = length;
+ if (lumaStride != 0) {
+ if (lumaStride < mImg.width) {
+ std::cerr << "Bad luma stride received" << std::endl;
+ return false;
+ }
+ mImg.luma_stride = lumaStride;
}
- result = malloc(length);
- if (read(fd, result, length) != static_cast<ssize_t>(length)) {
- close(fd);
- return false;
+ if (chromaStride != 0) {
+ if (mFormat == YCbCr_p010 && chromaStride < mImg.width) {
+ std::cerr << "Bad chroma stride received for format YCbCrP010" << std::endl;
+ return false;
+ }
+ if (mFormat == YCbCr_420 && chromaStride < (mImg.width >> 1)) {
+ std::cerr << "Bad chroma stride received for format YCbCr420" << std::endl;
+ return false;
+ }
+ mImg.chroma_stride = chromaStride;
}
- close(fd);
return true;
}
-static bool loadP010Image(const char *filename, jr_uncompressed_ptr img,
- bool isUVContiguous) {
- int fd = open(filename, O_CLOEXEC);
- if (fd < 0) {
+bool UhdrUnCompressedStructWrapper::setImageColorGamut(ultrahdr_color_gamut colorGamut) {
+ if (mLumaData.get() != nullptr) {
+ std::cerr << "Object has sailed, no further modifications are allowed" << std::endl;
return false;
}
- const int bpp = 2;
- int lumaStride = img->luma_stride == 0 ? img->width : img->luma_stride;
- int lumaSize = bpp * lumaStride * img->height;
- int chromaSize = bpp * (img->height / 2) *
- (isUVContiguous ? lumaStride : img->chroma_stride);
- img->data = malloc(lumaSize + (isUVContiguous ? chromaSize : 0));
- if (img->data == nullptr) {
- ALOGE("loadP010Image(): failed to allocate memory for luma data.");
+ mImg.colorGamut = colorGamut;
+ return true;
+}
+
+bool UhdrUnCompressedStructWrapper::allocateMemory() {
+ if (mImg.width == 0 || (mImg.width % 2 != 0) || mImg.height == 0 || (mImg.height % 2 != 0) ||
+ (mFormat != YCbCr_p010 && mFormat != YCbCr_420)) {
+ std::cerr << "Object in bad state, mem alloc failed" << std::endl;
return false;
}
- uint8_t *mem = static_cast<uint8_t *>(img->data);
- for (int i = 0; i < img->height; i++) {
- if (read(fd, mem, img->width * bpp) != img->width * bpp) {
- close(fd);
+ int lumaStride = mImg.luma_stride == 0 ? mImg.width : mImg.luma_stride;
+ int lumaSize = lumaStride * mImg.height * (mFormat == YCbCr_p010 ? 2 : 1);
+ int chromaSize = (mImg.height >> 1) * (mFormat == YCbCr_p010 ? 2 : 1);
+ if (mIsChromaContiguous) {
+ chromaSize *= lumaStride;
+ } else {
+ if (mImg.chroma_stride == 0) {
+ std::cerr << "Object in bad state, mem alloc failed" << std::endl;
return false;
}
- mem += lumaStride * bpp;
- }
- int chromaStride = lumaStride;
- if (!isUVContiguous) {
- img->chroma_data = malloc(chromaSize);
- if (img->chroma_data == nullptr) {
- ALOGE("loadP010Image(): failed to allocate memory for chroma data.");
- return false;
+ if (mFormat == YCbCr_p010) {
+ chromaSize *= mImg.chroma_stride;
+ } else {
+ chromaSize *= (mImg.chroma_stride * 2);
}
- mem = static_cast<uint8_t *>(img->chroma_data);
- chromaStride = img->chroma_stride;
}
- for (int i = 0; i < img->height / 2; i++) {
- if (read(fd, mem, img->width * bpp) != img->width * bpp) {
- close(fd);
- return false;
- }
- mem += chromaStride * bpp;
+ if (mIsChromaContiguous) {
+ mLumaData = std::make_unique<uint8_t[]>(lumaSize + chromaSize);
+ mImg.data = mLumaData.get();
+ mImg.chroma_data = nullptr;
+ } else {
+ mLumaData = std::make_unique<uint8_t[]>(lumaSize);
+ mImg.data = mLumaData.get();
+ mChromaData = std::make_unique<uint8_t[]>(chromaSize);
+ mImg.chroma_data = mChromaData.get();
}
- close(fd);
return true;
}
-class JpegRTest : public testing::Test {
-public:
- JpegRTest();
- ~JpegRTest();
-
-protected:
- virtual void SetUp();
- virtual void TearDown();
-
- struct jpegr_uncompressed_struct mRawP010Image{};
- struct jpegr_uncompressed_struct mRawP010ImageWithStride{};
- struct jpegr_uncompressed_struct mRawP010ImageWithChromaData{};
- struct jpegr_uncompressed_struct mRawYuv420Image{};
- struct jpegr_compressed_struct mJpegImage{};
-};
-
-JpegRTest::JpegRTest() {}
-JpegRTest::~JpegRTest() {}
-
-void JpegRTest::SetUp() {}
-void JpegRTest::TearDown() {
- free(mRawP010Image.data);
- free(mRawP010Image.chroma_data);
- free(mRawP010ImageWithStride.data);
- free(mRawP010ImageWithStride.chroma_data);
- free(mRawP010ImageWithChromaData.data);
- free(mRawP010ImageWithChromaData.chroma_data);
- free(mRawYuv420Image.data);
- free(mJpegImage.data);
+bool UhdrUnCompressedStructWrapper::loadRawResource(const char* fileName) {
+ if (!mImg.data) {
+ std::cerr << "memory is not allocated, read not possible" << std::endl;
+ return false;
+ }
+ std::ifstream ifd(fileName, std::ios::binary | std::ios::ate);
+ if (ifd.good()) {
+ int bpp = mFormat == YCbCr_p010 ? 2 : 1;
+ int size = ifd.tellg();
+ int length = mImg.width * mImg.height * bpp * 3 / 2; // 2x2 subsampling
+ if (size < length) {
+ std::cerr << "requested to read " << length << " bytes from file : " << fileName
+ << ", file contains only " << length << " bytes" << std::endl;
+ return false;
+ }
+ ifd.seekg(0, std::ios::beg);
+ int lumaStride = mImg.luma_stride == 0 ? mImg.width : mImg.luma_stride;
+ char* mem = static_cast<char*>(mImg.data);
+ for (int i = 0; i < mImg.height; i++) {
+ ifd.read(mem, mImg.width * bpp);
+ mem += lumaStride * bpp;
+ }
+ if (!mIsChromaContiguous) {
+ mem = static_cast<char*>(mImg.chroma_data);
+ }
+ int chromaStride;
+ if (mIsChromaContiguous) {
+ chromaStride = mFormat == YCbCr_p010 ? lumaStride : lumaStride / 2;
+ } else {
+ if (mFormat == YCbCr_p010) {
+ chromaStride = mImg.chroma_stride == 0 ? lumaStride : mImg.chroma_stride;
+ } else {
+ chromaStride = mImg.chroma_stride == 0 ? (lumaStride / 2) : mImg.chroma_stride;
+ }
+ }
+ if (mFormat == YCbCr_p010) {
+ for (int i = 0; i < mImg.height / 2; i++) {
+ ifd.read(mem, mImg.width * 2);
+ mem += chromaStride * 2;
+ }
+ } else {
+ for (int i = 0; i < mImg.height / 2; i++) {
+ ifd.read(mem, (mImg.width / 2));
+ mem += chromaStride;
+ }
+ for (int i = 0; i < mImg.height / 2; i++) {
+ ifd.read(mem, (mImg.width / 2));
+ mem += chromaStride;
+ }
+ }
+ return true;
+ }
+ std::cerr << "unable to open file : " << fileName << std::endl;
+ return false;
}
-class JpegRBenchmark : public JpegR {
-public:
- void BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image,
- ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr map);
- void BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map,
- ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest);
-private:
- const int kProfileCount = 10;
-};
-
-void JpegRBenchmark::BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image,
- jr_uncompressed_ptr p010Image,
- ultrahdr_metadata_ptr metadata,
- jr_uncompressed_ptr map) {
- ASSERT_EQ(yuv420Image->width, p010Image->width);
- ASSERT_EQ(yuv420Image->height, p010Image->height);
+jr_uncompressed_ptr UhdrUnCompressedStructWrapper::getImageHandle() {
+ return &mImg;
+}
- Timer genRecMapTime;
+UhdrCompressedStructWrapper::UhdrCompressedStructWrapper(uint32_t width, uint32_t height) {
+ mWidth = width;
+ mHeight = height;
+}
- timerStart(&genRecMapTime);
- for (auto i = 0; i < kProfileCount; i++) {
- ASSERT_EQ(OK, generateGainMap(
- yuv420Image, p010Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, metadata, map));
- if (i != kProfileCount - 1) delete[] static_cast<uint8_t *>(map->data);
+bool UhdrCompressedStructWrapper::allocateMemory() {
+ if (mWidth == 0 || (mWidth % 2 != 0) || mHeight == 0 || (mHeight % 2 != 0)) {
+ std::cerr << "Object in bad state, mem alloc failed" << std::endl;
+ return false;
}
- timerStop(&genRecMapTime);
-
- ALOGE("Generate Gain Map:- Res = %i x %i, time = %f ms",
- yuv420Image->width, yuv420Image->height,
- elapsedTime(&genRecMapTime) / (kProfileCount * 1000.f));
-
+ int maxLength = std::max(8 * 1024 /* min size 8kb */, (int)(mWidth * mHeight * 3 * 2));
+ mData = std::make_unique<uint8_t[]>(maxLength);
+ mImg.data = mData.get();
+ mImg.length = 0;
+ mImg.maxLength = maxLength;
+ return true;
}
-void JpegRBenchmark::BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image,
- jr_uncompressed_ptr map,
- ultrahdr_metadata_ptr metadata,
- jr_uncompressed_ptr dest) {
- Timer applyRecMapTime;
+jr_compressed_ptr UhdrCompressedStructWrapper::getImageHandle() {
+ return &mImg;
+}
- timerStart(&applyRecMapTime);
- for (auto i = 0; i < kProfileCount; i++) {
- ASSERT_EQ(OK, applyGainMap(yuv420Image, map, metadata, ULTRAHDR_OUTPUT_HDR_HLG,
- metadata->maxContentBoost /* displayBoost */, dest));
+static bool writeFile(const char* filename, void*& result, int length) {
+ std::ofstream ofd(filename, std::ios::binary);
+ if (ofd.is_open()) {
+ ofd.write(static_cast<char*>(result), length);
+ return true;
}
- timerStop(&applyRecMapTime);
-
- ALOGE("Apply Gain Map:- Res = %i x %i, time = %f ms",
- yuv420Image->width, yuv420Image->height,
- elapsedTime(&applyRecMapTime) / (kProfileCount * 1000.f));
+ std::cerr << "unable to write to file : " << filename << std::endl;
+ return false;
}
-TEST_F(JpegRTest, build) {
- // Force all of the gain map lib to be linked by calling all public functions.
- JpegR jpegRCodec;
- jpegRCodec.encodeJPEGR(nullptr, static_cast<ultrahdr_transfer_function>(0), nullptr, 0, nullptr);
- jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast<ultrahdr_transfer_function>(0),
- nullptr, 0, nullptr);
- jpegRCodec.encodeJPEGR(nullptr, nullptr, nullptr, static_cast<ultrahdr_transfer_function>(0),
- nullptr);
- jpegRCodec.encodeJPEGR(nullptr, nullptr, static_cast<ultrahdr_transfer_function>(0), nullptr);
- jpegRCodec.decodeJPEGR(nullptr, nullptr);
+static bool readFile(const char* fileName, void*& result, int maxLength, int& length) {
+ std::ifstream ifd(fileName, std::ios::binary | std::ios::ate);
+ if (ifd.good()) {
+ length = ifd.tellg();
+ if (length > maxLength) {
+ std::cerr << "not enough space to read file" << std::endl;
+ return false;
+ }
+ ifd.seekg(0, std::ios::beg);
+ ifd.read(static_cast<char*>(result), length);
+ return true;
+ }
+ std::cerr << "unable to read file : " << fileName << std::endl;
+ return false;
}
-/* Test Encode API-0 invalid arguments */
-TEST_F(JpegRTest, encodeAPI0ForInvalidArgs) {
- int ret;
-
- // we are not really compressing anything so lets keep allocs to a minimum
- jpegr_compressed_struct jpegR;
- jpegR.maxLength = 16 * sizeof(uint8_t);
- jpegR.data = malloc(jpegR.maxLength);
-
- JpegR jpegRCodec;
-
- // we are not really compressing anything so lets keep allocs to a minimum
- mRawP010ImageWithStride.data = malloc(16);
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
- mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-
- // test quality factor
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
- -1, nullptr)) << "fail, API allows bad jpeg quality factor";
-
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
- 101, nullptr)) << "fail, API allows bad jpeg quality factor";
-
- // test hdr transfer function
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, &jpegR,
- DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function";
-
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride,
- static_cast<ultrahdr_transfer_function>(ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
- &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function";
+void decodeJpegRImg(jr_compressed_ptr img, [[maybe_unused]] const char* outFileName) {
+ std::vector<uint8_t> iccData(0);
+ std::vector<uint8_t> exifData(0);
+ jpegr_info_struct info{0, 0, &iccData, &exifData};
+ JpegR jpegHdr;
+ ASSERT_EQ(OK, jpegHdr.getJPEGRInfo(img, &info));
+ ASSERT_EQ(kImageWidth, info.width);
+ ASSERT_EQ(kImageHeight, info.height);
+ size_t outSize = info.width * info.height * 8;
+ std::unique_ptr<uint8_t[]> data = std::make_unique<uint8_t[]>(outSize);
+ jpegr_uncompressed_struct destImage{};
+ destImage.data = data.get();
+ ASSERT_EQ(OK, jpegHdr.decodeJPEGR(img, &destImage));
+ ASSERT_EQ(kImageWidth, destImage.width);
+ ASSERT_EQ(kImageHeight, destImage.height);
+#ifdef DUMP_OUTPUT
+ if (!writeFile(outFileName, destImage.data, outSize)) {
+ std::cerr << "unable to write output file" << std::endl;
+ }
+#endif
+}
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride,
- static_cast<ultrahdr_transfer_function>(-10),
- &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function";
+// ============================================================================
+// Unit Tests
+// ============================================================================
+
+// Test Encode API-0 invalid arguments
+TEST(JpegRTest, EncodeAPI0WithInvalidArgs) {
+ JpegR uHdrLib;
+
+ UhdrCompressedStructWrapper jpgImg(16, 16);
+ ASSERT_TRUE(jpgImg.allocateMemory());
+
+ // test quality factor and transfer function
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), -1, nullptr),
+ OK)
+ << "fail, API allows bad jpeg quality factor";
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), 101, nullptr),
+ OK)
+ << "fail, API allows bad jpeg quality factor";
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+ static_cast<ultrahdr_transfer_function>(
+ ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+ static_cast<ultrahdr_transfer_function>(-10),
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ }
// test dest
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr,
- DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr dest";
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr, kQuality,
+ nullptr),
+ OK)
+ << "fail, API allows nullptr dest";
+ UhdrCompressedStructWrapper jpgImg2(16, 16);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows nullptr dest";
+ }
// test p010 input
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
- DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr p010 image";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
- DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad p010 color gamut";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.colorGamut = static_cast<ultrahdr_color_gamut>(
- ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
- DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad p010 color gamut";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH - 1;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
- DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image width";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT - 1;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
- DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image height";
-
- mRawP010ImageWithStride.width = 0;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
- DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image width";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = 0;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
- DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image height";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH - 2;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
- DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad luma stride";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
- mRawP010ImageWithStride.chroma_data = mRawP010ImageWithStride.data;
- mRawP010ImageWithStride.chroma_stride = TEST_IMAGE_WIDTH - 2;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
- DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad chroma stride";
-
- mRawP010ImageWithStride.chroma_data = nullptr;
-
- free(jpegR.data);
+ {
+ ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows nullptr p010 image";
+
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows nullptr p010 image";
+ }
+
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad p010 color gamut";
+
+ UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(
+ static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1)));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad p010 color gamut";
+ }
+
+ {
+ const int kWidth = 32, kHeight = 32;
+ UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ auto rawImgP010 = rawImg.getImageHandle();
+
+ rawImgP010->width = kWidth - 1;
+ rawImgP010->height = kHeight;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image width";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight - 1;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image height";
+
+ rawImgP010->width = 0;
+ rawImgP010->height = kHeight;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image width";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = 0;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image height";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->luma_stride = kWidth - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad luma stride";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->luma_stride = kWidth + 64;
+ rawImgP010->chroma_data = rawImgP010->data;
+ rawImgP010->chroma_stride = kWidth - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad chroma stride";
+ }
}
/* Test Encode API-1 invalid arguments */
-TEST_F(JpegRTest, encodeAPI1ForInvalidArgs) {
- int ret;
-
- // we are not really compressing anything so lets keep allocs to a minimum
- jpegr_compressed_struct jpegR;
- jpegR.maxLength = 16 * sizeof(uint8_t);
- jpegR.data = malloc(jpegR.maxLength);
-
- JpegR jpegRCodec;
-
- // we are not really compressing anything so lets keep allocs to a minimum
- mRawP010ImageWithStride.data = malloc(16);
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
- mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-
- // we are not really compressing anything so lets keep allocs to a minimum
- mRawYuv420Image.data = malloc(16);
- mRawYuv420Image.width = TEST_IMAGE_WIDTH;
- mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
- mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
-
- // test quality factor
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR, -1, nullptr)) << "fail, API allows bad jpeg quality factor";
-
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR, 101, nullptr)) << "fail, API allows bad jpeg quality factor";
-
- // test hdr transfer function
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image,
- ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, &jpegR, DEFAULT_JPEG_QUALITY,
- nullptr)) << "fail, API allows bad hdr transfer function";
-
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image,
- static_cast<ultrahdr_transfer_function>(ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
- &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function";
-
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image,
- static_cast<ultrahdr_transfer_function>(-10),
- &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function";
+TEST(JpegRTest, EncodeAPI1WithInvalidArgs) {
+ JpegR uHdrLib;
+
+ UhdrCompressedStructWrapper jpgImg(16, 16);
+ ASSERT_TRUE(jpgImg.allocateMemory());
+
+ // test quality factor and transfer function
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), -1, nullptr),
+ OK)
+ << "fail, API allows bad jpeg quality factor";
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), 101, nullptr),
+ OK)
+ << "fail, API allows bad jpeg quality factor";
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ static_cast<ultrahdr_transfer_function>(
+ ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ static_cast<ultrahdr_transfer_function>(-10),
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ }
// test dest
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- nullptr, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr dest";
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr, kQuality,
+ nullptr),
+ OK)
+ << "fail, API allows nullptr dest";
+ UhdrCompressedStructWrapper jpgImg2(16, 16);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows nullptr dest";
+ }
// test p010 input
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- nullptr, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
- DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr p010 image";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad p010 color gamut";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.colorGamut = static_cast<ultrahdr_color_gamut>(
- ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad p010 color gamut";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH - 1;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image width";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT - 1;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image height";
-
- mRawP010ImageWithStride.width = 0;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image width";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = 0;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image height";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH - 2;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad luma stride";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
- mRawP010ImageWithStride.chroma_data = mRawP010ImageWithStride.data;
- mRawP010ImageWithStride.chroma_stride = TEST_IMAGE_WIDTH - 2;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad chroma stride";
+ {
+ UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows nullptr p010 image";
+
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows nullptr p010 image";
+ }
+
+ {
+ const int kWidth = 32, kHeight = 32;
+ UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ auto rawImgP010 = rawImg.getImageHandle();
+ UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ auto rawImg420 = rawImg2.getImageHandle();
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad p010 color gamut";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->colorGamut =
+ static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad p010 color gamut";
+
+ rawImgP010->width = kWidth - 1;
+ rawImgP010->height = kHeight;
+ rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image width";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight - 1;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image height";
+
+ rawImgP010->width = 0;
+ rawImgP010->height = kHeight;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image width";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = 0;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image height";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->luma_stride = kWidth - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad luma stride";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->luma_stride = kWidth + 64;
+ rawImgP010->chroma_data = rawImgP010->data;
+ rawImgP010->chroma_stride = kWidth - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad chroma stride";
+ }
// test 420 input
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
- mRawP010ImageWithStride.chroma_data = nullptr;
- mRawP010ImageWithStride.chroma_stride = 0;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
- DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr for 420 image";
-
- mRawYuv420Image.width = TEST_IMAGE_WIDTH;
- mRawYuv420Image.height = TEST_IMAGE_HEIGHT - 2;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad 420 image width";
-
- mRawYuv420Image.width = TEST_IMAGE_WIDTH - 2;
- mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad 420 image height";
-
- mRawYuv420Image.width = TEST_IMAGE_WIDTH;
- mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
- mRawYuv420Image.luma_stride = TEST_IMAGE_STRIDE;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad luma stride for 420";
-
- mRawYuv420Image.width = TEST_IMAGE_WIDTH;
- mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
- mRawYuv420Image.luma_stride = 0;
- mRawYuv420Image.chroma_data = mRawYuv420Image.data;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows chroma pointer for 420";
-
- mRawYuv420Image.width = TEST_IMAGE_WIDTH;
- mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
- mRawYuv420Image.luma_stride = 0;
- mRawYuv420Image.chroma_data = nullptr;
- mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad 420 color gamut";
-
- mRawYuv420Image.colorGamut = static_cast<ultrahdr_color_gamut>(
- ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad 420 color gamut";
-
- free(jpegR.data);
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows nullptr 420 image";
+
+ UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows nullptr 420 image";
+ }
+ {
+ const int kWidth = 32, kHeight = 32;
+ UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ auto rawImgP010 = rawImg.getImageHandle();
+ UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ auto rawImg420 = rawImg2.getImageHandle();
+
+ rawImg420->width = kWidth;
+ rawImg420->height = kHeight;
+ rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad 420 color gamut";
+
+ rawImg420->width = kWidth;
+ rawImg420->height = kHeight;
+ rawImg420->colorGamut =
+ static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad 420 color gamut";
+
+ rawImg420->width = kWidth - 1;
+ rawImg420->height = kHeight;
+ rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image width for 420";
+
+ rawImg420->width = kWidth;
+ rawImg420->height = kHeight - 1;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image height for 420";
+
+ rawImg420->width = 0;
+ rawImg420->height = kHeight;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image width for 420";
+
+ rawImg420->width = kWidth;
+ rawImg420->height = 0;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image height for 420";
+
+ rawImg420->width = kWidth;
+ rawImg420->height = kHeight;
+ rawImg420->luma_stride = kWidth - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad luma stride for 420";
+
+ rawImg420->width = kWidth;
+ rawImg420->height = kHeight;
+ rawImg420->luma_stride = 0;
+ rawImg420->chroma_data = rawImgP010->data;
+ rawImg420->chroma_stride = kWidth / 2 - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad chroma stride for 420";
+ }
}
/* Test Encode API-2 invalid arguments */
-TEST_F(JpegRTest, encodeAPI2ForInvalidArgs) {
- int ret;
-
- // we are not really compressing anything so lets keep allocs to a minimum
- jpegr_compressed_struct jpegR;
- jpegR.maxLength = 16 * sizeof(uint8_t);
- jpegR.data = malloc(jpegR.maxLength);
-
- JpegR jpegRCodec;
-
- // we are not really compressing anything so lets keep allocs to a minimum
- mRawP010ImageWithStride.data = malloc(16);
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
- mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-
- // we are not really compressing anything so lets keep allocs to a minimum
- mRawYuv420Image.data = malloc(16);
- mRawYuv420Image.width = TEST_IMAGE_WIDTH;
- mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
- mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
-
- // test hdr transfer function
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
- ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
- &jpegR)) << "fail, API allows bad hdr transfer function";
-
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
- static_cast<ultrahdr_transfer_function>(ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
- &jpegR)) << "fail, API allows bad hdr transfer function";
-
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
- static_cast<ultrahdr_transfer_function>(-10),
- &jpegR)) << "fail, API allows bad hdr transfer function";
+TEST(JpegRTest, EncodeAPI2WithInvalidArgs) {
+ JpegR uHdrLib;
+
+ UhdrCompressedStructWrapper jpgImg(16, 16);
+ ASSERT_TRUE(jpgImg.allocateMemory());
+
+ // test quality factor and transfer function
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ jpgImg.getImageHandle(),
+ static_cast<ultrahdr_transfer_function>(
+ ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ jpgImg.getImageHandle(),
+ static_cast<ultrahdr_transfer_function>(-10),
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ }
// test dest
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
- ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr)) << "fail, API allows nullptr dest";
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr),
+ OK)
+ << "fail, API allows nullptr dest";
+ UhdrCompressedStructWrapper jpgImg2(16, 16);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr dest";
+ }
+
+ // test compressed image
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), nullptr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr for compressed image";
+ UhdrCompressedStructWrapper jpgImg2(16, 16);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ jpgImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr for compressed image";
+ }
// test p010 input
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- nullptr, &mRawYuv420Image, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows nullptr p010 image";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
- ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows bad p010 color gamut";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.colorGamut = static_cast<ultrahdr_color_gamut>(
- ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
- ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows bad p010 color gamut";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH - 1;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
- ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad image width";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT - 1;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
- ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad image height";
-
- mRawP010ImageWithStride.width = 0;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
- ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad image width";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = 0;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
- ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad image height";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH - 2;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
- ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad luma stride";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
- mRawP010ImageWithStride.chroma_data = mRawP010ImageWithStride.data;
- mRawP010ImageWithStride.chroma_stride = TEST_IMAGE_WIDTH - 2;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
- ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows bad chroma stride";
+ {
+ UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, rawImg2.getImageHandle(), jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr p010 image";
+
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr p010 image";
+ }
+
+ {
+ const int kWidth = 32, kHeight = 32;
+ UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ auto rawImgP010 = rawImg.getImageHandle();
+ UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ auto rawImg420 = rawImg2.getImageHandle();
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad p010 color gamut";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->colorGamut =
+ static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad p010 color gamut";
+
+ rawImgP010->width = kWidth - 1;
+ rawImgP010->height = kHeight;
+ rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image width";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight - 1;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image height";
+
+ rawImgP010->width = 0;
+ rawImgP010->height = kHeight;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image width";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = 0;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image height";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->luma_stride = kWidth - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad luma stride";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->luma_stride = kWidth + 64;
+ rawImgP010->chroma_data = rawImgP010->data;
+ rawImgP010->chroma_stride = kWidth - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad chroma stride";
+ }
// test 420 input
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
- mRawP010ImageWithStride.chroma_data = nullptr;
- mRawP010ImageWithStride.chroma_stride = 0;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, nullptr, &jpegR,
- ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows nullptr for 420 image";
-
- mRawYuv420Image.width = TEST_IMAGE_WIDTH;
- mRawYuv420Image.height = TEST_IMAGE_HEIGHT - 2;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
- ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows bad 420 image width";
-
- mRawYuv420Image.width = TEST_IMAGE_WIDTH - 2;
- mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
- ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows bad 420 image height";
-
- mRawYuv420Image.width = TEST_IMAGE_WIDTH;
- mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
- mRawYuv420Image.luma_stride = TEST_IMAGE_STRIDE;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
- ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows bad luma stride for 420";
-
- mRawYuv420Image.width = TEST_IMAGE_WIDTH;
- mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
- mRawYuv420Image.luma_stride = 0;
- mRawYuv420Image.chroma_data = mRawYuv420Image.data;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
- ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows chroma pointer for 420";
-
- mRawYuv420Image.width = TEST_IMAGE_WIDTH;
- mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
- mRawYuv420Image.luma_stride = 0;
- mRawYuv420Image.chroma_data = nullptr;
- mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
- ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows bad 420 color gamut";
-
- mRawYuv420Image.colorGamut = static_cast<ultrahdr_color_gamut>(
- ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
- ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows bad 420 color gamut";
-
- // bad compressed image
- mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &mRawYuv420Image, nullptr,
- ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows bad 420 color gamut";
-
- free(jpegR.data);
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr 420 image";
+
+ UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr 420 image";
+ }
+ {
+ const int kWidth = 32, kHeight = 32;
+ UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ auto rawImgP010 = rawImg.getImageHandle();
+ UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ auto rawImg420 = rawImg2.getImageHandle();
+
+ rawImg420->width = kWidth;
+ rawImg420->height = kHeight;
+ rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad 420 color gamut";
+
+ rawImg420->width = kWidth;
+ rawImg420->height = kHeight;
+ rawImg420->colorGamut =
+ static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad 420 color gamut";
+
+ rawImg420->width = kWidth - 1;
+ rawImg420->height = kHeight;
+ rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image width for 420";
+
+ rawImg420->width = kWidth;
+ rawImg420->height = kHeight - 1;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image height for 420";
+
+ rawImg420->width = 0;
+ rawImg420->height = kHeight;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image width for 420";
+
+ rawImg420->width = kWidth;
+ rawImg420->height = 0;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image height for 420";
+
+ rawImg420->width = kWidth;
+ rawImg420->height = kHeight;
+ rawImg420->luma_stride = kWidth - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad luma stride for 420";
+
+ rawImg420->width = kWidth;
+ rawImg420->height = kHeight;
+ rawImg420->luma_stride = 0;
+ rawImg420->chroma_data = rawImgP010->data;
+ rawImg420->chroma_stride = kWidth / 2 - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad chroma stride for 420";
+ }
}
/* Test Encode API-3 invalid arguments */
-TEST_F(JpegRTest, encodeAPI3ForInvalidArgs) {
- int ret;
-
- // we are not really compressing anything so lets keep allocs to a minimum
- jpegr_compressed_struct jpegR;
- jpegR.maxLength = 16 * sizeof(uint8_t);
- jpegR.data = malloc(jpegR.maxLength);
-
- JpegR jpegRCodec;
-
- // we are not really compressing anything so lets keep allocs to a minimum
- mRawP010ImageWithStride.data = malloc(16);
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
- mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-
- // test hdr transfer function
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
- &jpegR)) << "fail, API allows bad hdr transfer function";
-
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &jpegR,
- static_cast<ultrahdr_transfer_function>(ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
- &jpegR)) << "fail, API allows bad hdr transfer function";
-
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &jpegR, static_cast<ultrahdr_transfer_function>(-10),
- &jpegR)) << "fail, API allows bad hdr transfer function";
+TEST(JpegRTest, EncodeAPI3WithInvalidArgs) {
+ JpegR uHdrLib;
+
+ UhdrCompressedStructWrapper jpgImg(16, 16);
+ ASSERT_TRUE(jpgImg.allocateMemory());
+
+ // test quality factor and transfer function
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
+ static_cast<ultrahdr_transfer_function>(
+ ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
+ static_cast<ultrahdr_transfer_function>(-10),
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ }
// test dest
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- nullptr)) << "fail, API allows nullptr dest";
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr),
+ OK)
+ << "fail, API allows nullptr dest";
+ UhdrCompressedStructWrapper jpgImg2(16, 16);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr dest";
+ }
+
+ // test compressed image
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr for compressed image";
+ UhdrCompressedStructWrapper jpgImg2(16, 16);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr for compressed image";
+ }
// test p010 input
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- nullptr, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows nullptr p010 image";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows bad p010 color gamut";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.colorGamut = static_cast<ultrahdr_color_gamut>(
- ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows bad p010 color gamut";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH - 1;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows bad image width";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT - 1;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows bad image height";
-
- mRawP010ImageWithStride.width = 0;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows bad image width";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = 0;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows bad image height";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH - 2;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows bad luma stride";
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
- mRawP010ImageWithStride.chroma_data = mRawP010ImageWithStride.data;
- mRawP010ImageWithStride.chroma_stride = TEST_IMAGE_WIDTH - 2;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows bad chroma stride";
- mRawP010ImageWithStride.chroma_data = nullptr;
-
- // bad compressed image
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR)) << "fail, API allows bad 420 color gamut";
-
- free(jpegR.data);
+ {
+ ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr p010 image";
+
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr p010 image";
+ }
+
+ {
+ const int kWidth = 32, kHeight = 32;
+ UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ auto rawImgP010 = rawImg.getImageHandle();
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad p010 color gamut";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->colorGamut =
+ static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad p010 color gamut";
+
+ rawImgP010->width = kWidth - 1;
+ rawImgP010->height = kHeight;
+ rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image width";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight - 1;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image height";
+
+ rawImgP010->width = 0;
+ rawImgP010->height = kHeight;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image width";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = 0;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image height";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->luma_stride = kWidth - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad luma stride";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->luma_stride = kWidth + 64;
+ rawImgP010->chroma_data = rawImgP010->data;
+ rawImgP010->chroma_stride = kWidth - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad chroma stride";
+ }
}
/* Test Encode API-4 invalid arguments */
-TEST_F(JpegRTest, encodeAPI4ForInvalidArgs) {
- int ret;
-
- // we are not really compressing anything so lets keep allocs to a minimum
- jpegr_compressed_struct jpegR;
- jpegR.maxLength = 16 * sizeof(uint8_t);
- jpegR.data = malloc(jpegR.maxLength);
-
- JpegR jpegRCodec;
+TEST(JpegRTest, EncodeAPI4WithInvalidArgs) {
+ UhdrCompressedStructWrapper jpgImg(16, 16);
+ ASSERT_TRUE(jpgImg.allocateMemory());
+ UhdrCompressedStructWrapper jpgImg2(16, 16);
+ JpegR uHdrLib;
// test dest
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &jpegR, &jpegR, nullptr, nullptr)) << "fail, API allows nullptr dest";
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), nullptr, nullptr),
+ OK)
+ << "fail, API allows nullptr dest";
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), nullptr,
+ jpgImg2.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr dest";
// test primary image
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- nullptr, &jpegR, nullptr, &jpegR)) << "fail, API allows nullptr primary image";
+ ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, jpgImg.getImageHandle(), nullptr, jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr primary image";
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg2.getImageHandle(), jpgImg.getImageHandle(), nullptr,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr primary image";
// test gain map
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &jpegR, nullptr, nullptr, &jpegR)) << "fail, API allows nullptr gainmap image";
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), nullptr, nullptr, jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr gain map image";
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg2.getImageHandle(), nullptr,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr gain map image";
// test metadata
ultrahdr_metadata_struct good_metadata;
@@ -832,82 +1254,95 @@ TEST_F(JpegRTest, encodeAPI4ForInvalidArgs) {
ultrahdr_metadata_struct metadata = good_metadata;
metadata.version = "1.1";
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata version";
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad metadata version";
metadata = good_metadata;
metadata.minContentBoost = 3.0f;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata content boost";
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad metadata content boost";
metadata = good_metadata;
metadata.gamma = -0.1f;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata gamma";
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad metadata gamma";
metadata = good_metadata;
metadata.offsetSdr = -0.1f;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata offset sdr";
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad metadata offset sdr";
metadata = good_metadata;
metadata.offsetHdr = -0.1f;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata offset hdr";
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad metadata offset hdr";
metadata = good_metadata;
metadata.hdrCapacityMax = 0.5f;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata hdr capacity max";
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad metadata hdr capacity max";
metadata = good_metadata;
metadata.hdrCapacityMin = 0.5f;
- EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
- &jpegR, nullptr, &metadata, &jpegR)) << "fail, API allows bad metadata hdr capacity min";
-
- free(jpegR.data);
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad metadata hdr capacity min";
}
/* Test Decode API invalid arguments */
-TEST_F(JpegRTest, decodeAPIForInvalidArgs) {
- int ret;
+TEST(JpegRTest, DecodeAPIWithInvalidArgs) {
+ JpegR uHdrLib;
- // we are not really compressing anything so lets keep allocs to a minimum
- jpegr_compressed_struct jpegR;
- jpegR.maxLength = 16 * sizeof(uint8_t);
- jpegR.data = malloc(jpegR.maxLength);
-
- // we are not really decoding anything so lets keep allocs to a minimum
- mRawP010Image.data = malloc(16);
-
- JpegR jpegRCodec;
+ UhdrCompressedStructWrapper jpgImg(16, 16);
+ jpegr_uncompressed_struct destImage{};
+ size_t outSize = 16 * 16 * 8;
+ std::unique_ptr<uint8_t[]> data = std::make_unique<uint8_t[]>(outSize);
+ destImage.data = data.get();
// test jpegr image
- EXPECT_NE(OK, jpegRCodec.decodeJPEGR(
- nullptr, &mRawP010Image)) << "fail, API allows nullptr for jpegr img";
+ ASSERT_NE(uHdrLib.decodeJPEGR(nullptr, &destImage), OK)
+ << "fail, API allows nullptr for jpegr img";
+ ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage), OK)
+ << "fail, API allows nullptr for jpegr img";
+ ASSERT_TRUE(jpgImg.allocateMemory());
// test dest image
- EXPECT_NE(OK, jpegRCodec.decodeJPEGR(
- &jpegR, nullptr)) << "fail, API allows nullptr for dest";
+ ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), nullptr), OK)
+ << "fail, API allows nullptr for dest";
+ destImage.data = nullptr;
+ ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage), OK)
+ << "fail, API allows nullptr for dest";
+ destImage.data = data.get();
// test max display boost
- EXPECT_NE(OK, jpegRCodec.decodeJPEGR(
- &jpegR, &mRawP010Image, 0.5)) << "fail, API allows invalid max display boost";
+ ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, 0.5), OK)
+ << "fail, API allows invalid max display boost";
// test output format
- EXPECT_NE(OK, jpegRCodec.decodeJPEGR(
- &jpegR, &mRawP010Image, 0.5, nullptr,
- static_cast<ultrahdr_output_format>(-1))) << "fail, API allows invalid output format";
-
- EXPECT_NE(OK, jpegRCodec.decodeJPEGR(
- &jpegR, &mRawP010Image, 0.5, nullptr,
- static_cast<ultrahdr_output_format>(ULTRAHDR_OUTPUT_MAX + 1)))
- << "fail, API allows invalid output format";
-
- free(jpegR.data);
+ ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, FLT_MAX, nullptr,
+ static_cast<ultrahdr_output_format>(-1)),
+ OK)
+ << "fail, API allows invalid output format";
+ ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, FLT_MAX, nullptr,
+ static_cast<ultrahdr_output_format>(ULTRAHDR_OUTPUT_MAX + 1)),
+ OK)
+ << "fail, API allows invalid output format";
}
-TEST_F(JpegRTest, writeXmpThenRead) {
+TEST(JpegRTest, writeXmpThenRead) {
ultrahdr_metadata_struct metadata_expected;
metadata_expected.version = "1.0";
metadata_expected.maxContentBoost = 1.25f;
@@ -918,16 +1353,16 @@ TEST_F(JpegRTest, writeXmpThenRead) {
metadata_expected.hdrCapacityMin = 1.0f;
metadata_expected.hdrCapacityMax = metadata_expected.maxContentBoost;
const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
- const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
+ const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
std::string xmp = generateXmpForSecondaryImage(metadata_expected);
std::vector<uint8_t> xmpData;
xmpData.reserve(nameSpaceLength + xmp.size());
xmpData.insert(xmpData.end(), reinterpret_cast<const uint8_t*>(nameSpace.c_str()),
- reinterpret_cast<const uint8_t*>(nameSpace.c_str()) + nameSpaceLength);
+ reinterpret_cast<const uint8_t*>(nameSpace.c_str()) + nameSpaceLength);
xmpData.insert(xmpData.end(), reinterpret_cast<const uint8_t*>(xmp.c_str()),
- reinterpret_cast<const uint8_t*>(xmp.c_str()) + xmp.size());
+ reinterpret_cast<const uint8_t*>(xmp.c_str()) + xmp.size());
ultrahdr_metadata_struct metadata_read;
EXPECT_TRUE(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read));
@@ -940,436 +1375,661 @@ TEST_F(JpegRTest, writeXmpThenRead) {
EXPECT_FLOAT_EQ(metadata_expected.hdrCapacityMax, metadata_read.hdrCapacityMax);
}
-/* Test Encode API-0 */
-TEST_F(JpegRTest, encodeFromP010) {
- int ret;
-
- mRawP010Image.width = TEST_IMAGE_WIDTH;
- mRawP010Image.height = TEST_IMAGE_HEIGHT;
- mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
- // Load input files.
- if (!loadP010Image(RAW_P010_IMAGE, &mRawP010Image, true)) {
- FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
- }
-
- JpegR jpegRCodec;
-
- jpegr_compressed_struct jpegR;
- jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
- jpegR.data = malloc(jpegR.maxLength);
- ret = jpegRCodec.encodeJPEGR(
- &mRawP010Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY,
- nullptr);
- if (ret != OK) {
- FAIL() << "Error code is " << ret;
- }
-
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH + 128;
- mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
- // Load input files.
- if (!loadP010Image(RAW_P010_IMAGE, &mRawP010ImageWithStride, true)) {
- FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
- }
-
- jpegr_compressed_struct jpegRWithStride;
- jpegRWithStride.maxLength = jpegR.length;
- jpegRWithStride.data = malloc(jpegRWithStride.maxLength);
- ret = jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegRWithStride,
- DEFAULT_JPEG_QUALITY, nullptr);
- if (ret != OK) {
- FAIL() << "Error code is " << ret;
- }
- ASSERT_EQ(jpegR.length, jpegRWithStride.length)
- << "Same input is yielding different output";
- ASSERT_EQ(0, memcmp(jpegR.data, jpegRWithStride.data, jpegR.length))
- << "Same input is yielding different output";
-
- mRawP010ImageWithChromaData.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithChromaData.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithChromaData.luma_stride = TEST_IMAGE_WIDTH + 64;
- mRawP010ImageWithChromaData.chroma_stride = TEST_IMAGE_WIDTH + 256;
- mRawP010ImageWithChromaData.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
- // Load input files.
- if (!loadP010Image(RAW_P010_IMAGE, &mRawP010ImageWithChromaData, false)) {
- FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
- }
- jpegr_compressed_struct jpegRWithChromaData;
- jpegRWithChromaData.maxLength = jpegR.length;
- jpegRWithChromaData.data = malloc(jpegRWithChromaData.maxLength);
- ret = jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithChromaData, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegRWithChromaData, DEFAULT_JPEG_QUALITY, nullptr);
- if (ret != OK) {
- FAIL() << "Error code is " << ret;
- }
- ASSERT_EQ(jpegR.length, jpegRWithChromaData.length)
- << "Same input is yielding different output";
- ASSERT_EQ(0, memcmp(jpegR.data, jpegRWithChromaData.data, jpegR.length))
- << "Same input is yielding different output";
-
- free(jpegR.data);
- free(jpegRWithStride.data);
- free(jpegRWithChromaData.data);
-}
+class JpegRAPIEncodeAndDecodeTest
+ : public ::testing::TestWithParam<std::tuple<ultrahdr_color_gamut, ultrahdr_color_gamut>> {
+public:
+ JpegRAPIEncodeAndDecodeTest()
+ : mP010ColorGamut(std::get<0>(GetParam())), mYuv420ColorGamut(std::get<1>(GetParam())){};
-/* Test Encode API-0 and decode */
-TEST_F(JpegRTest, encodeFromP010ThenDecode) {
- int ret;
-
- // Load input files.
- if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
- FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
- }
- mRawP010Image.width = TEST_IMAGE_WIDTH;
- mRawP010Image.height = TEST_IMAGE_HEIGHT;
- mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-
- JpegR jpegRCodec;
-
- jpegr_compressed_struct jpegR;
- jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
- jpegR.data = malloc(jpegR.maxLength);
- ret = jpegRCodec.encodeJPEGR(
- &mRawP010Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY,
- nullptr);
- if (ret != OK) {
- FAIL() << "Error code is " << ret;
- }
- if (SAVE_ENCODING_RESULT) {
- // Output image data to file
- std::string filePath = "/sdcard/Documents/encoded_from_p010_input.jpgr";
- std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
- if (!imageFile.is_open()) {
- ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
- }
- imageFile.write((const char*)jpegR.data, jpegR.length);
- }
-
- jpegr_uncompressed_struct decodedJpegR;
- int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8;
- decodedJpegR.data = malloc(decodedJpegRSize);
- ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
- if (ret != OK) {
- FAIL() << "Error code is " << ret;
- }
- if (SAVE_DECODING_RESULT) {
- // Output image data to file
- std::string filePath = "/sdcard/Documents/decoded_from_p010_input.rgb";
- std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
- if (!imageFile.is_open()) {
- ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
- }
- imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize);
+ const ultrahdr_color_gamut mP010ColorGamut;
+ const ultrahdr_color_gamut mYuv420ColorGamut;
+};
+
+/* Test Encode API-0 and Decode */
+TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI0AndDecodeTest) {
+ // reference encode
+ UhdrUnCompressedStructWrapper rawImg(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ ASSERT_TRUE(rawImg.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg.allocateMemory());
+ JpegR uHdrLib;
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK);
+ // encode with luma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2.setImageStride(kImageWidth + 18, 0));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma and chroma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2.setImageStride(kImageWidth + 18, kImageWidth + 28));
+ ASSERT_TRUE(rawImg2.setChromaMode(false));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with chroma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2.setImageStride(0, kImageWidth + 34));
+ ASSERT_TRUE(rawImg2.setChromaMode(false));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma and chroma stride set but no chroma ptr
+ {
+ UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2.setImageStride(kImageWidth, kImageWidth + 38));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
}
- free(jpegR.data);
- free(decodedJpegR.data);
+ auto jpg1 = jpgImg.getImageHandle();
+#ifdef DUMP_OUTPUT
+ if (!writeFile("encode_api0_output.jpeg", jpg1->data, jpg1->length)) {
+ std::cerr << "unable to write output file" << std::endl;
+ }
+#endif
+
+ ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api0_output.rgb"));
}
-/* Test Encode API-0 (with stride) and decode */
-TEST_F(JpegRTest, encodeFromP010WithStrideThenDecode) {
- int ret;
-
- // Load input files.
- if (!loadFile(RAW_P010_IMAGE_WITH_STRIDE, mRawP010ImageWithStride.data, nullptr)) {
- FAIL() << "Load file " << RAW_P010_IMAGE_WITH_STRIDE << " failed";
- }
- mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
- mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
- mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
- mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-
- JpegR jpegRCodec;
-
- jpegr_compressed_struct jpegR;
- jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
- jpegR.data = malloc(jpegR.maxLength);
- ret = jpegRCodec.encodeJPEGR(
- &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
- DEFAULT_JPEG_QUALITY, nullptr);
- if (ret != OK) {
- FAIL() << "Error code is " << ret;
- }
- if (SAVE_ENCODING_RESULT) {
- // Output image data to file
- std::string filePath = "/sdcard/Documents/encoded_from_p010_input.jpgr";
- std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
- if (!imageFile.is_open()) {
- ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
- }
- imageFile.write((const char*)jpegR.data, jpegR.length);
- }
-
- jpegr_uncompressed_struct decodedJpegR;
- int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8;
- decodedJpegR.data = malloc(decodedJpegRSize);
- ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
- if (ret != OK) {
- FAIL() << "Error code is " << ret;
- }
- if (SAVE_DECODING_RESULT) {
- // Output image data to file
- std::string filePath = "/sdcard/Documents/decoded_from_p010_input.rgb";
- std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
- if (!imageFile.is_open()) {
- ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
- }
- imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize);
+/* Test Encode API-1 and Decode */
+TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI1AndDecodeTest) {
+ UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImgP010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImgP010.allocateMemory());
+ ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
+ UhdrUnCompressedStructWrapper rawImg420(kImageWidth, kImageHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg420.setImageColorGamut(mYuv420ColorGamut));
+ ASSERT_TRUE(rawImg420.allocateMemory());
+ ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName));
+ UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg.allocateMemory());
+ JpegR uHdrLib;
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg420.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK);
+ // encode with luma stride set p010
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, 0));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma and chroma stride set p010
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, kImageWidth + 256));
+ ASSERT_TRUE(rawImg2P010.setChromaMode(false));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with chroma stride set p010
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(0, kImageWidth + 64));
+ ASSERT_TRUE(rawImg2P010.setChromaMode(false));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma and chroma stride set but no chroma ptr p010
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 64, kImageWidth + 256));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma stride set 420
+ {
+ UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
+ ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 14, 0));
+ ASSERT_TRUE(rawImg2420.allocateMemory());
+ ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma and chroma stride set 420
+ {
+ UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
+ ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 46, kImageWidth / 2 + 34));
+ ASSERT_TRUE(rawImg2420.setChromaMode(false));
+ ASSERT_TRUE(rawImg2420.allocateMemory());
+ ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with chroma stride set 420
+ {
+ UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
+ ASSERT_TRUE(rawImg2420.setImageStride(0, kImageWidth / 2 + 38));
+ ASSERT_TRUE(rawImg2420.setChromaMode(false));
+ ASSERT_TRUE(rawImg2420.allocateMemory());
+ ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma and chroma stride set but no chroma ptr 420
+ {
+ UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
+ ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 26, kImageWidth / 2 + 44));
+ ASSERT_TRUE(rawImg2420.allocateMemory());
+ ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
}
- free(jpegR.data);
- free(decodedJpegR.data);
-}
+ auto jpg1 = jpgImg.getImageHandle();
-/* Test Encode API-1 and decode */
-TEST_F(JpegRTest, encodeFromRawHdrAndSdrThenDecode) {
- int ret;
-
- // Load input files.
- if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
- FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
- }
- mRawP010Image.width = TEST_IMAGE_WIDTH;
- mRawP010Image.height = TEST_IMAGE_HEIGHT;
- mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-
- if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) {
- FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
- }
- mRawYuv420Image.width = TEST_IMAGE_WIDTH;
- mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
- mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
-
- JpegR jpegRCodec;
-
- jpegr_compressed_struct jpegR;
- jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
- jpegR.data = malloc(jpegR.maxLength);
- ret = jpegRCodec.encodeJPEGR(
- &mRawP010Image, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
- DEFAULT_JPEG_QUALITY, nullptr);
- if (ret != OK) {
- FAIL() << "Error code is " << ret;
- }
- if (SAVE_ENCODING_RESULT) {
- // Output image data to file
- std::string filePath = "/sdcard/Documents/encoded_from_p010_yuv420p_input.jpgr";
- std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
- if (!imageFile.is_open()) {
- ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
- }
- imageFile.write((const char*)jpegR.data, jpegR.length);
- }
-
- jpegr_uncompressed_struct decodedJpegR;
- int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8;
- decodedJpegR.data = malloc(decodedJpegRSize);
- ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
- if (ret != OK) {
- FAIL() << "Error code is " << ret;
- }
- if (SAVE_DECODING_RESULT) {
- // Output image data to file
- std::string filePath = "/sdcard/Documents/decoded_from_p010_yuv420p_input.rgb";
- std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
- if (!imageFile.is_open()) {
- ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
- }
- imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize);
+#ifdef DUMP_OUTPUT
+ if (!writeFile("encode_api1_output.jpeg", jpg1->data, jpg1->length)) {
+ std::cerr << "unable to write output file" << std::endl;
}
+#endif
- free(jpegR.data);
- free(decodedJpegR.data);
+ ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api1_output.rgb"));
}
-/* Test Encode API-2 and decode */
-TEST_F(JpegRTest, encodeFromRawHdrAndSdrAndJpegThenDecode) {
- int ret;
-
- // Load input files.
- if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
- FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+/* Test Encode API-2 and Decode */
+TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI2AndDecodeTest) {
+ UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImgP010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImgP010.allocateMemory());
+ ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
+ UhdrUnCompressedStructWrapper rawImg420(kImageWidth, kImageHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg420.setImageColorGamut(mYuv420ColorGamut));
+ ASSERT_TRUE(rawImg420.allocateMemory());
+ ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName));
+ UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg.allocateMemory());
+ UhdrCompressedStructWrapper jpgSdr(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgSdr.allocateMemory());
+ auto sdr = jpgSdr.getImageHandle();
+ ASSERT_TRUE(readFile(kSdrJpgFileName, sdr->data, sdr->maxLength, sdr->length));
+ JpegR uHdrLib;
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg420.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK);
+ // encode with luma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, 0));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
}
- mRawP010Image.width = TEST_IMAGE_WIDTH;
- mRawP010Image.height = TEST_IMAGE_HEIGHT;
- mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-
- if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) {
- FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+ // encode with luma and chroma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, kImageWidth + 256));
+ ASSERT_TRUE(rawImg2P010.setChromaMode(false));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with chroma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(0, kImageWidth + 64));
+ ASSERT_TRUE(rawImg2P010.setChromaMode(false));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
+ ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 128, 0));
+ ASSERT_TRUE(rawImg2420.allocateMemory());
+ ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma and chroma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
+ ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 128, kImageWidth + 256));
+ ASSERT_TRUE(rawImg2420.setChromaMode(false));
+ ASSERT_TRUE(rawImg2420.allocateMemory());
+ ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with chroma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
+ ASSERT_TRUE(rawImg2420.setImageStride(0, kImageWidth + 64));
+ ASSERT_TRUE(rawImg2420.setChromaMode(false));
+ ASSERT_TRUE(rawImg2420.allocateMemory());
+ ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
}
- mRawYuv420Image.width = TEST_IMAGE_WIDTH;
- mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
- mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
- if (!loadFile(JPEG_IMAGE, mJpegImage.data, &mJpegImage.length)) {
- FAIL() << "Load file " << JPEG_IMAGE << " failed";
+ auto jpg1 = jpgImg.getImageHandle();
+
+#ifdef DUMP_OUTPUT
+ if (!writeFile("encode_api2_output.jpeg", jpg1->data, jpg1->length)) {
+ std::cerr << "unable to write output file" << std::endl;
}
- mJpegImage.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
+#endif
- JpegR jpegRCodec;
+ ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api2_output.rgb"));
+}
- jpegr_compressed_struct jpegR;
- jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
- jpegR.data = malloc(jpegR.maxLength);
- ret = jpegRCodec.encodeJPEGR(
- &mRawP010Image, &mRawYuv420Image, &mJpegImage, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
- &jpegR);
- if (ret != OK) {
- FAIL() << "Error code is " << ret;
+/* Test Encode API-3 and Decode */
+TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI3AndDecodeTest) {
+ UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImgP010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImgP010.allocateMemory());
+ ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg.allocateMemory());
+ UhdrCompressedStructWrapper jpgSdr(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgSdr.allocateMemory());
+ auto sdr = jpgSdr.getImageHandle();
+ ASSERT_TRUE(readFile(kSdrJpgFileName, sdr->data, sdr->maxLength, sdr->length));
+ JpegR uHdrLib;
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK);
+ // encode with luma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, 0));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
}
- if (SAVE_ENCODING_RESULT) {
- // Output image data to file
- std::string filePath = "/sdcard/Documents/encoded_from_p010_yuv420p_jpeg_input.jpgr";
- std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
- if (!imageFile.is_open()) {
- ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
- }
- imageFile.write((const char*)jpegR.data, jpegR.length);
- }
-
- jpegr_uncompressed_struct decodedJpegR;
- int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8;
- decodedJpegR.data = malloc(decodedJpegRSize);
- ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
- if (ret != OK) {
- FAIL() << "Error code is " << ret;
- }
- if (SAVE_DECODING_RESULT) {
- // Output image data to file
- std::string filePath = "/sdcard/Documents/decoded_from_p010_yuv420p_jpeg_input.rgb";
- std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
- if (!imageFile.is_open()) {
- ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
- }
- imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize);
+ // encode with luma and chroma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, kImageWidth + 256));
+ ASSERT_TRUE(rawImg2P010.setChromaMode(false));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with chroma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(0, kImageWidth + 64));
+ ASSERT_TRUE(rawImg2P010.setChromaMode(false));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma and chroma stride set and no chroma ptr
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 32, kImageWidth + 256));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+
+ auto jpg1 = jpgImg.getImageHandle();
+
+#ifdef DUMP_OUTPUT
+ if (!writeFile("encode_api3_output.jpeg", jpg1->data, jpg1->length)) {
+ std::cerr << "unable to write output file" << std::endl;
}
+#endif
- free(jpegR.data);
- free(decodedJpegR.data);
+ ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api3_output.rgb"));
}
-/* Test Encode API-3 and decode */
-TEST_F(JpegRTest, encodeFromJpegThenDecode) {
- int ret;
-
- // Load input files.
- if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
- FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
- }
- mRawP010Image.width = TEST_IMAGE_WIDTH;
- mRawP010Image.height = TEST_IMAGE_HEIGHT;
- mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-
- if (SAVE_INPUT_RGBA) {
- size_t rgbaSize = mRawP010Image.width * mRawP010Image.height * sizeof(uint32_t);
- uint32_t *data = (uint32_t *)malloc(rgbaSize);
-
- for (size_t y = 0; y < mRawP010Image.height; ++y) {
- for (size_t x = 0; x < mRawP010Image.width; ++x) {
- Color hdr_yuv_gamma = getP010Pixel(&mRawP010Image, x, y);
- Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
- uint32_t rgba1010102 = colorToRgba1010102(hdr_rgb_gamma);
- size_t pixel_idx = x + y * mRawP010Image.width;
- reinterpret_cast<uint32_t*>(data)[pixel_idx] = rgba1010102;
- }
- }
+INSTANTIATE_TEST_SUITE_P(
+ JpegRAPIParameterizedTests, JpegRAPIEncodeAndDecodeTest,
+ ::testing::Combine(::testing::Values(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3,
+ ULTRAHDR_COLORGAMUT_BT2100),
+ ::testing::Values(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3,
+ ULTRAHDR_COLORGAMUT_BT2100)));
- // Output image data to file
- std::string filePath = "/sdcard/Documents/input_from_p010.rgb10";
- std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
- if (!imageFile.is_open()) {
- ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
- }
- imageFile.write((const char*)data, rgbaSize);
- free(data);
- }
- if (!loadFile(JPEG_IMAGE, mJpegImage.data, &mJpegImage.length)) {
- FAIL() << "Load file " << JPEG_IMAGE << " failed";
- }
- mJpegImage.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
-
- JpegR jpegRCodec;
-
- jpegr_compressed_struct jpegR;
- jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
- jpegR.data = malloc(jpegR.maxLength);
- ret = jpegRCodec.encodeJPEGR(
- &mRawP010Image, &mJpegImage, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR);
- if (ret != OK) {
- FAIL() << "Error code is " << ret;
- }
- if (SAVE_ENCODING_RESULT) {
- // Output image data to file
- std::string filePath = "/sdcard/Documents/encoded_from_p010_jpeg_input.jpgr";
- std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
- if (!imageFile.is_open()) {
- ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
- }
- imageFile.write((const char*)jpegR.data, jpegR.length);
- }
-
- jpegr_uncompressed_struct decodedJpegR;
- int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 8;
- decodedJpegR.data = malloc(decodedJpegRSize);
- ret = jpegRCodec.decodeJPEGR(&jpegR, &decodedJpegR);
- if (ret != OK) {
- FAIL() << "Error code is " << ret;
- }
- if (SAVE_DECODING_RESULT) {
- // Output image data to file
- std::string filePath = "/sdcard/Documents/decoded_from_p010_jpeg_input.rgb";
- std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
- if (!imageFile.is_open()) {
- ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
- }
- imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize);
+// ============================================================================
+// Profiling
+// ============================================================================
+
+class Profiler {
+public:
+ void timerStart() { gettimeofday(&mStartingTime, nullptr); }
+
+ void timerStop() { gettimeofday(&mEndingTime, nullptr); }
+
+ int64_t elapsedTime() {
+ struct timeval elapsedMicroseconds;
+ elapsedMicroseconds.tv_sec = mEndingTime.tv_sec - mStartingTime.tv_sec;
+ elapsedMicroseconds.tv_usec = mEndingTime.tv_usec - mStartingTime.tv_usec;
+ return elapsedMicroseconds.tv_sec * 1000000 + elapsedMicroseconds.tv_usec;
}
- free(jpegR.data);
- free(decodedJpegR.data);
-}
+private:
+ struct timeval mStartingTime;
+ struct timeval mEndingTime;
+};
+
+class JpegRBenchmark : public JpegR {
+public:
+ void BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image,
+ ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr map);
+ void BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map,
+ ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest);
-TEST_F(JpegRTest, ProfileGainMapFuncs) {
- const size_t kWidth = TEST_IMAGE_WIDTH;
- const size_t kHeight = TEST_IMAGE_HEIGHT;
+private:
+ const int kProfileCount = 10;
+};
+
+void JpegRBenchmark::BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image,
+ jr_uncompressed_ptr p010Image,
+ ultrahdr_metadata_ptr metadata,
+ jr_uncompressed_ptr map) {
+ ASSERT_EQ(yuv420Image->width, p010Image->width);
+ ASSERT_EQ(yuv420Image->height, p010Image->height);
+ Profiler profileGenerateMap;
+ profileGenerateMap.timerStart();
+ for (auto i = 0; i < kProfileCount; i++) {
+ ASSERT_EQ(OK,
+ generateGainMap(yuv420Image, p010Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ metadata, map));
+ if (i != kProfileCount - 1) delete[] static_cast<uint8_t*>(map->data);
+ }
+ profileGenerateMap.timerStop();
+ ALOGE("Generate Gain Map:- Res = %i x %i, time = %f ms", yuv420Image->width, yuv420Image->height,
+ profileGenerateMap.elapsedTime() / (kProfileCount * 1000.f));
+}
- // Load input files.
- if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
- FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+void JpegRBenchmark::BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map,
+ ultrahdr_metadata_ptr metadata,
+ jr_uncompressed_ptr dest) {
+ Profiler profileRecMap;
+ profileRecMap.timerStart();
+ for (auto i = 0; i < kProfileCount; i++) {
+ ASSERT_EQ(OK,
+ applyGainMap(yuv420Image, map, metadata, ULTRAHDR_OUTPUT_HDR_HLG,
+ metadata->maxContentBoost /* displayBoost */, dest));
}
- mRawP010Image.width = kWidth;
- mRawP010Image.height = kHeight;
- mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+ profileRecMap.timerStop();
+ ALOGE("Apply Gain Map:- Res = %i x %i, time = %f ms", yuv420Image->width, yuv420Image->height,
+ profileRecMap.elapsedTime() / (kProfileCount * 1000.f));
+}
- if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) {
- FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+TEST(JpegRTest, ProfileGainMapFuncs) {
+ UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImgP010.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImgP010.allocateMemory());
+ ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
+ UhdrUnCompressedStructWrapper rawImg420(kImageWidth, kImageHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg420.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg420.allocateMemory());
+ ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName));
+ ultrahdr_metadata_struct metadata = {.version = "1.0"};
+ jpegr_uncompressed_struct map = {.data = NULL,
+ .width = 0,
+ .height = 0,
+ .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
+ {
+ auto rawImg = rawImgP010.getImageHandle();
+ if (rawImg->luma_stride == 0) rawImg->luma_stride = rawImg->width;
+ if (!rawImg->chroma_data) {
+ uint16_t* data = reinterpret_cast<uint16_t*>(rawImg->data);
+ rawImg->chroma_data = data + rawImg->luma_stride * rawImg->height;
+ rawImg->chroma_stride = rawImg->luma_stride;
+ }
+ }
+ {
+ auto rawImg = rawImg420.getImageHandle();
+ if (rawImg->luma_stride == 0) rawImg->luma_stride = rawImg->width;
+ if (!rawImg->chroma_data) {
+ uint8_t* data = reinterpret_cast<uint8_t*>(rawImg->data);
+ rawImg->chroma_data = data + rawImg->luma_stride * rawImg->height;
+ rawImg->chroma_stride = rawImg->luma_stride / 2;
+ }
}
- mRawYuv420Image.width = kWidth;
- mRawYuv420Image.height = kHeight;
- mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
JpegRBenchmark benchmark;
+ ASSERT_NO_FATAL_FAILURE(benchmark.BenchmarkGenerateGainMap(rawImg420.getImageHandle(),
+ rawImgP010.getImageHandle(), &metadata,
+ &map));
- ultrahdr_metadata_struct metadata = { .version = "1.0" };
-
- jpegr_uncompressed_struct map = { .data = NULL,
+ const int dstSize = kImageWidth * kImageWidth * 4;
+ auto bufferDst = std::make_unique<uint8_t[]>(dstSize);
+ jpegr_uncompressed_struct dest = {.data = bufferDst.get(),
.width = 0,
.height = 0,
- .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED };
-
- benchmark.BenchmarkGenerateGainMap(&mRawYuv420Image, &mRawP010Image, &metadata, &map);
-
- const int dstSize = mRawYuv420Image.width * mRawYuv420Image.height * 4;
- auto bufferDst = std::make_unique<uint8_t[]>(dstSize);
- jpegr_uncompressed_struct dest = { .data = bufferDst.get(),
- .width = 0,
- .height = 0,
- .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED };
+ .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
- benchmark.BenchmarkApplyGainMap(&mRawYuv420Image, &map, &metadata, &dest);
+ ASSERT_NO_FATAL_FAILURE(
+ benchmark.BenchmarkApplyGainMap(rawImg420.getImageHandle(), &map, &metadata, &dest));
}
} // namespace android::ultrahdr