diff options
78 files changed, 2424 insertions, 838 deletions
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp index 2684f048f8..2ae61b9603 100644 --- a/cmds/servicemanager/ServiceManager.cpp +++ b/cmds/servicemanager/ServiceManager.cpp @@ -612,7 +612,8 @@ void ServiceManager::binderDied(const wp<IBinder>& who) { } void ServiceManager::tryStartService(const std::string& name) { - ALOGI("Since '%s' could not be found, trying to start it as a lazy AIDL service", + ALOGI("Since '%s' could not be found, trying to start it as a lazy AIDL service. (if it's not " + "configured to be a lazy service, it may be stuck starting or still starting).", name.c_str()); std::thread([=] { diff --git a/data/etc/android.software.opengles.deqp.level-2023-03-01.xml b/data/etc/android.software.opengles.deqp.level-2023-03-01.xml new file mode 100644 index 0000000000..d0b594c73d --- /dev/null +++ b/data/etc/android.software.opengles.deqp.level-2023-03-01.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2021 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 is the standard feature indicating that the device passes OpenGL ES + dEQP tests associated with date 2023-03-01 (0x07E70301). --> +<permissions> + <feature name="android.software.opengles.deqp.level" version="132580097" /> +</permissions> diff --git a/data/etc/android.software.vulkan.deqp.level-2023-03-01.xml b/data/etc/android.software.vulkan.deqp.level-2023-03-01.xml new file mode 100644 index 0000000000..6ae248ac3c --- /dev/null +++ b/data/etc/android.software.vulkan.deqp.level-2023-03-01.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2021 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 is the standard feature indicating that the device passes Vulkan dEQP + tests associated with date 2023-03-01 (0x07E70301). --> +<permissions> + <feature name="android.software.vulkan.deqp.level" version="132580097" /> +</permissions> diff --git a/libs/binder/ndk/include_cpp/android/binder_auto_utils.h b/libs/binder/ndk/include_cpp/android/binder_auto_utils.h index fccc0afa89..fe5f35c266 100644 --- a/libs/binder/ndk/include_cpp/android/binder_auto_utils.h +++ b/libs/binder/ndk/include_cpp/android/binder_auto_utils.h @@ -30,11 +30,11 @@ #include <android/binder_internal_logging.h> #include <android/binder_parcel.h> #include <android/binder_status.h> - #include <assert.h> - #include <unistd.h> + #include <cstddef> +#include <iostream> #include <string> namespace ndk { @@ -315,6 +315,11 @@ class ScopedAStatus : public impl::ScopedAResource<AStatus*, AStatus_delete, nul } }; +static inline std::ostream& operator<<(std::ostream& os, const ScopedAStatus& status) { + return os << status.getDescription(); + return os; +} + /** * Convenience wrapper. See AIBinder_DeathRecipient. */ diff --git a/libs/binder/ndk/tests/binderVendorDoubleLoadTest.cpp b/libs/binder/ndk/tests/binderVendorDoubleLoadTest.cpp index f3cd21822d..43b2cb8577 100644 --- a/libs/binder/ndk/tests/binderVendorDoubleLoadTest.cpp +++ b/libs/binder/ndk/tests/binderVendorDoubleLoadTest.cpp @@ -106,7 +106,7 @@ TEST(DoubleBinder, CallIntoNdk) { std::string outString; ScopedAStatus status = server->RepeatString("foo", &outString); EXPECT_EQ(STATUS_OK, AStatus_getExceptionCode(status.get())) - << serviceName << " " << status.getDescription(); + << serviceName << " " << status; EXPECT_EQ("foo", outString) << serviceName; } } diff --git a/libs/binder/rust/tests/parcel_fuzzer/Android.bp b/libs/binder/rust/tests/parcel_fuzzer/Android.bp new file mode 100644 index 0000000000..28e0200fe1 --- /dev/null +++ b/libs/binder/rust/tests/parcel_fuzzer/Android.bp @@ -0,0 +1,25 @@ +package { + // See: http://go/android-license-faq + default_applicable_licenses: ["frameworks_native_license"], +} + +rust_fuzz { + name: "parcel_fuzzer_rs", + srcs: [ + "parcel_fuzzer.rs", + ], + rustlibs: [ + "libarbitrary", + "libnum_traits", + "libbinder_rs", + "libbinder_random_parcel_rs", + "binderReadParcelIface-rust", + ], + + fuzz_config: { + cc: [ + "waghpawan@google.com", + "smoreland@google.com", + ], + }, +} diff --git a/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs b/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs new file mode 100644 index 0000000000..c5c7719df2 --- /dev/null +++ b/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs @@ -0,0 +1,161 @@ +/* + * Copyright (C) 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. + */ + +#![allow(missing_docs)] +#![no_main] + +#[macro_use] +extern crate libfuzzer_sys; + +mod read_utils; + +use crate::read_utils::READ_FUNCS; +use binder::binder_impl::{ + Binder, BorrowedParcel, IBinderInternal, Parcel, Stability, TransactionCode, +}; +use binder::{ + declare_binder_interface, BinderFeatures, Interface, Parcelable, ParcelableHolder, SpIBinder, + StatusCode, +}; +use binder_random_parcel_rs::create_random_parcel; +use libfuzzer_sys::arbitrary::Arbitrary; + +#[derive(Arbitrary, Debug)] +enum ReadOperation { + SetDataPosition { pos: i32 }, + GetDataSize, + ReadParcelableHolder { is_vintf: bool }, + ReadBasicTypes { instructions: Vec<usize> }, +} + +#[derive(Arbitrary, Debug)] +enum Operation<'a> { + Transact { code: u32, flag: u32, data: &'a [u8] }, + Append { start: i32, len: i32, data1: &'a [u8], data2: &'a [u8], append_all: bool }, + Read { read_operations: Vec<ReadOperation>, data: &'a [u8] }, +} + +/// Interface to fuzz transact with random parcel +pub trait BinderTransactTest: Interface {} + +declare_binder_interface! { + BinderTransactTest["Binder_Transact_Test"] { + native: BnBinderTransactTest(on_transact), + proxy: BpBinderTransactTest, + } +} + +impl BinderTransactTest for Binder<BnBinderTransactTest> {} + +impl BinderTransactTest for BpBinderTransactTest {} + +impl BinderTransactTest for () {} + +fn on_transact( + _service: &dyn BinderTransactTest, + _code: TransactionCode, + _parcel: &BorrowedParcel<'_>, + _reply: &mut BorrowedParcel<'_>, +) -> Result<(), StatusCode> { + Err(StatusCode::UNKNOWN_ERROR) +} + +fn do_transact(code: u32, data: &[u8], flag: u32) { + let p: Parcel = create_random_parcel(data); + let spibinder: Option<SpIBinder> = + Some(BnBinderTransactTest::new_binder((), BinderFeatures::default()).as_binder()); + let _reply = spibinder.submit_transact(code, p, flag); +} + +fn do_append_fuzz(start: i32, len: i32, data1: &[u8], data2: &[u8], append_all: bool) { + let mut p1 = create_random_parcel(data1); + let p2 = create_random_parcel(data2); + + // Fuzz both append methods + if append_all { + match p1.append_all_from(&p2) { + Ok(result) => result, + Err(e) => { + println!("Error occurred while appending a parcel using append_all_from: {:?}", e) + } + } + } else { + match p1.append_from(&p2, start, len) { + Ok(result) => result, + Err(e) => { + println!("Error occurred while appending a parcel using append_from: {:?}", e) + } + } + }; +} + +fn do_read_fuzz(read_operations: Vec<ReadOperation>, data: &[u8]) { + let parcel = create_random_parcel(data); + + for operation in read_operations { + match operation { + ReadOperation::SetDataPosition { pos } => { + unsafe { + // Safety: Safe if pos is less than current size of the parcel. + // It relies on C++ code for bound checks + match parcel.set_data_position(pos) { + Ok(result) => result, + Err(e) => println!("error occurred while setting data position: {:?}", e), + } + } + } + + ReadOperation::GetDataSize => { + let data_size = parcel.get_data_size(); + println!("data size from parcel: {:?}", data_size); + } + + ReadOperation::ReadParcelableHolder { is_vintf } => { + let stability = if is_vintf { Stability::Vintf } else { Stability::Local }; + let mut holder: ParcelableHolder = ParcelableHolder::new(stability); + match holder.read_from_parcel(parcel.borrowed_ref()) { + Ok(result) => result, + Err(err) => { + println!("error occurred while reading from parcel: {:?}", err) + } + } + } + + ReadOperation::ReadBasicTypes { instructions } => { + for instruction in instructions.iter() { + let read_index = instruction % READ_FUNCS.len(); + READ_FUNCS[read_index](parcel.borrowed_ref()); + } + } + } + } +} + +fuzz_target!(|operation: Operation| { + match operation { + Operation::Transact { code, flag, data } => { + do_transact(code, data, flag); + } + + Operation::Append { start, len, data1, data2, append_all } => { + do_append_fuzz(start, len, data1, data2, append_all); + } + + Operation::Read { read_operations, data } => { + do_read_fuzz(read_operations, data); + } + } +}); diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/Android.bp b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/Android.bp index 6fe4fcd876..43a309409d 100644 --- a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/Android.bp +++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/Android.bp @@ -14,6 +14,8 @@ rust_bindgen { "--size_t-is-usize", "--allowlist-function", "createRandomParcel", + "--allowlist-function", + "fuzzRustService", ], shared_libs: [ "libc++", diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/Android.bp b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/Android.bp new file mode 100644 index 0000000000..43e407cef1 --- /dev/null +++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/Android.bp @@ -0,0 +1,33 @@ +package { + // See: http://go/android-license-faq + default_applicable_licenses: ["frameworks_native_license"], +} + +aidl_interface { + name: "testServiceInterface", + srcs: ["ITestService.aidl"], + unstable: true, + backend: { + rust: { + enabled: true, + }, + }, +} + +rust_fuzz { + name: "example_service_fuzzer", + srcs: [ + "service_fuzzer.rs", + ], + rustlibs: [ + "libbinder_rs", + "libbinder_random_parcel_rs", + "testServiceInterface-rust", + ], + fuzz_config: { + cc: [ + "waghpawan@google.com", + "smoreland@google.com", + ], + }, +} diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/ITestService.aidl b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/ITestService.aidl new file mode 100644 index 0000000000..8ce655862f --- /dev/null +++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/ITestService.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 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. + */ + +interface ITestService { + boolean repeatData(boolean token); +}
\ No newline at end of file diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/service_fuzzer.rs b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/service_fuzzer.rs new file mode 100644 index 0000000000..a427f28e29 --- /dev/null +++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/service_fuzzer.rs @@ -0,0 +1,39 @@ +/* + * Copyright (C) 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. + */ + +#![allow(missing_docs)] +#![no_main] +#[macro_use] +extern crate libfuzzer_sys; + +use binder::{self, BinderFeatures, Interface}; +use binder_random_parcel_rs::fuzz_service; +use testServiceInterface::aidl::ITestService::{self, BnTestService}; + +struct TestService; + +impl Interface for TestService {} + +impl ITestService::ITestService for TestService { + fn repeatData(&self, token: bool) -> binder::Result<bool> { + Ok(token) + } +} + +fuzz_target!(|data: &[u8]| { + let service = BnTestService::new_binder(TestService, BinderFeatures::default()); + fuzz_service(&mut service.as_binder(), data); +}); diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/src/lib.rs b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/src/lib.rs index ee3b6f813a..1bbd6742f2 100644 --- a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/src/lib.rs +++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/src/lib.rs @@ -16,7 +16,8 @@ use binder::binder_impl::Parcel; use binder::unstable_api::{AParcel, AsNative}; -use binder_random_parcel_bindgen::createRandomParcel; +use binder::SpIBinder; +use binder_random_parcel_bindgen::{createRandomParcel, fuzzRustService}; use std::os::raw::c_void; /// This API creates a random parcel to be used by fuzzers @@ -31,3 +32,13 @@ pub fn create_random_parcel(fuzzer_data: &[u8]) -> Parcel { } parcel } + +/// This API automatically fuzzes provided service +pub fn fuzz_service(binder: &mut SpIBinder, fuzzer_data: &[u8]) { + let ptr = binder.as_native_mut() as *mut c_void; + unsafe { + // Safety: `SpIBinder::as_native_mut` and `slice::as_ptr` always + // return valid pointers. + fuzzRustService(ptr, fuzzer_data.as_ptr(), fuzzer_data.len()); + } +} diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/wrappers/RandomParcelWrapper.hpp b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/wrappers/RandomParcelWrapper.hpp index 167a64e548..831bd5660c 100644 --- a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/wrappers/RandomParcelWrapper.hpp +++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/wrappers/RandomParcelWrapper.hpp @@ -19,4 +19,7 @@ extern "C" { // This API is used by rust to fill random parcel. void createRandomParcel(void* aParcel, const uint8_t* data, size_t len); + + // This API is used by fuzzers to automatically fuzz aidl services + void fuzzRustService(void* binder, const uint8_t* data, size_t len); }
\ No newline at end of file diff --git a/libs/binder/rust/tests/parcel_fuzzer/read_utils.rs b/libs/binder/rust/tests/parcel_fuzzer/read_utils.rs new file mode 100644 index 0000000000..d2bfde1022 --- /dev/null +++ b/libs/binder/rust/tests/parcel_fuzzer/read_utils.rs @@ -0,0 +1,133 @@ +/* + * Copyright (C) 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. + */ + +use binder::binder_impl::BorrowedParcel; +use binder::{ParcelFileDescriptor, Parcelable, SpIBinder}; +use binderReadParcelIface::aidl::EmptyParcelable::EmptyParcelable; +use binderReadParcelIface::aidl::GenericDataParcelable::GenericDataParcelable; +use binderReadParcelIface::aidl::SingleDataParcelable::SingleDataParcelable; + +macro_rules! read_parcel_interface { + ($data_type:ty) => { + |parcel: &BorrowedParcel<'_>| { + let _res = parcel.read::<$data_type>(); + } + }; +} + +#[derive(Debug, Default)] +pub struct SomeParcelable { + pub data: i32, +} + +impl binder::Parcelable for SomeParcelable { + fn write_to_parcel( + &self, + parcel: &mut binder::binder_impl::BorrowedParcel, + ) -> std::result::Result<(), binder::StatusCode> { + parcel.sized_write(|subparcel| subparcel.write(&self.data)) + } + + fn read_from_parcel( + &mut self, + parcel: &binder::binder_impl::BorrowedParcel, + ) -> std::result::Result<(), binder::StatusCode> { + parcel.sized_read(|subparcel| match subparcel.read() { + Ok(result) => { + self.data = result; + Ok(()) + } + Err(e) => Err(e), + }) + } +} + +binder::impl_deserialize_for_parcelable!(SomeParcelable); + +pub const READ_FUNCS: &[fn(&BorrowedParcel<'_>)] = &[ + //read basic types + read_parcel_interface!(bool), + read_parcel_interface!(i8), + read_parcel_interface!(i32), + read_parcel_interface!(i64), + read_parcel_interface!(f32), + read_parcel_interface!(f64), + read_parcel_interface!(u16), + read_parcel_interface!(u32), + read_parcel_interface!(u64), + read_parcel_interface!(String), + //read vec of basic types + read_parcel_interface!(Vec<i8>), + read_parcel_interface!(Vec<i32>), + read_parcel_interface!(Vec<i64>), + read_parcel_interface!(Vec<f32>), + read_parcel_interface!(Vec<f64>), + read_parcel_interface!(Vec<u16>), + read_parcel_interface!(Vec<u32>), + read_parcel_interface!(Vec<u64>), + read_parcel_interface!(Vec<String>), + read_parcel_interface!(Option<Vec<i8>>), + read_parcel_interface!(Option<Vec<i32>>), + read_parcel_interface!(Option<Vec<i64>>), + read_parcel_interface!(Option<Vec<f32>>), + read_parcel_interface!(Option<Vec<f64>>), + read_parcel_interface!(Option<Vec<u16>>), + read_parcel_interface!(Option<Vec<u32>>), + read_parcel_interface!(Option<Vec<u64>>), + read_parcel_interface!(Option<Vec<String>>), + read_parcel_interface!(ParcelFileDescriptor), + read_parcel_interface!(Vec<Option<ParcelFileDescriptor>>), + read_parcel_interface!(Option<Vec<ParcelFileDescriptor>>), + read_parcel_interface!(Option<Vec<Option<ParcelFileDescriptor>>>), + read_parcel_interface!(SpIBinder), + read_parcel_interface!(Vec<Option<SpIBinder>>), + read_parcel_interface!(Option<Vec<SpIBinder>>), + read_parcel_interface!(Option<Vec<Option<SpIBinder>>>), + read_parcel_interface!(SomeParcelable), + read_parcel_interface!(Vec<Option<SomeParcelable>>), + read_parcel_interface!(Option<Vec<SomeParcelable>>), + read_parcel_interface!(Option<Vec<Option<SomeParcelable>>>), + // Fuzz read_from_parcel for AIDL generated parcelables + |parcel| { + let mut empty_parcelable: EmptyParcelable = EmptyParcelable::default(); + match empty_parcelable.read_from_parcel(parcel) { + Ok(result) => result, + Err(e) => { + println!("EmptyParcelable: error occurred while reading from a parcel: {:?}", e) + } + } + }, + |parcel| { + let mut single_parcelable: SingleDataParcelable = SingleDataParcelable::default(); + match single_parcelable.read_from_parcel(parcel) { + Ok(result) => result, + Err(e) => println!( + "SingleDataParcelable: error occurred while reading from a parcel: {:?}", + e + ), + } + }, + |parcel| { + let mut generic_parcelable: GenericDataParcelable = GenericDataParcelable::default(); + match generic_parcelable.read_from_parcel(parcel) { + Ok(result) => result, + Err(e) => println!( + "GenericDataParcelable: error occurred while reading from a parcel: {:?}", + e + ), + } + }, +]; diff --git a/libs/binder/tests/parcel_fuzzer/Android.bp b/libs/binder/tests/parcel_fuzzer/Android.bp index 3904e1d4cc..61a24127d6 100644 --- a/libs/binder/tests/parcel_fuzzer/Android.bp +++ b/libs/binder/tests/parcel_fuzzer/Android.bp @@ -20,6 +20,9 @@ aidl_interface { java: { enabled: false, }, + rust: { + enabled: true, + }, }, } diff --git a/libs/binder/tests/parcel_fuzzer/libbinder_ndk_driver.cpp b/libs/binder/tests/parcel_fuzzer/libbinder_ndk_driver.cpp index 462ef9a5e9..a1fb70131e 100644 --- a/libs/binder/tests/parcel_fuzzer/libbinder_ndk_driver.cpp +++ b/libs/binder/tests/parcel_fuzzer/libbinder_ndk_driver.cpp @@ -29,3 +29,12 @@ void fuzzService(AIBinder* binder, FuzzedDataProvider&& provider) { } } // namespace android + +extern "C" { +// This API is used by fuzzers to automatically fuzz aidl services +void fuzzRustService(void* binder, const uint8_t* data, size_t len) { + AIBinder* aiBinder = static_cast<AIBinder*>(binder); + FuzzedDataProvider provider(data, len); + android::fuzzService(aiBinder, std::move(provider)); +} +} // extern "C" diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 9c2ce0f242..9e175ec42e 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2375,42 +2375,22 @@ status_t SurfaceComposerClient::getActiveDisplayMode(const sp<IBinder>& display, return NAME_NOT_FOUND; } -status_t SurfaceComposerClient::setDesiredDisplayModeSpecs( - const sp<IBinder>& displayToken, ui::DisplayModeId defaultMode, bool allowGroupSwitching, - float primaryRefreshRateMin, float primaryRefreshRateMax, float appRequestRefreshRateMin, - float appRequestRefreshRateMax) { +status_t SurfaceComposerClient::setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, + const gui::DisplayModeSpecs& specs) { binder::Status status = - ComposerServiceAIDL::getComposerService() - ->setDesiredDisplayModeSpecs(displayToken, defaultMode, allowGroupSwitching, - primaryRefreshRateMin, primaryRefreshRateMax, - appRequestRefreshRateMin, - appRequestRefreshRateMax); + ComposerServiceAIDL::getComposerService()->setDesiredDisplayModeSpecs(displayToken, + specs); return statusTFromBinderStatus(status); } status_t SurfaceComposerClient::getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, - ui::DisplayModeId* outDefaultMode, - bool* outAllowGroupSwitching, - float* outPrimaryRefreshRateMin, - float* outPrimaryRefreshRateMax, - float* outAppRequestRefreshRateMin, - float* outAppRequestRefreshRateMax) { - if (!outDefaultMode || !outAllowGroupSwitching || !outPrimaryRefreshRateMin || - !outPrimaryRefreshRateMax || !outAppRequestRefreshRateMin || !outAppRequestRefreshRateMax) { + gui::DisplayModeSpecs* outSpecs) { + if (!outSpecs) { return BAD_VALUE; } - gui::DisplayModeSpecs specs; binder::Status status = ComposerServiceAIDL::getComposerService()->getDesiredDisplayModeSpecs(displayToken, - &specs); - if (status.isOk()) { - *outDefaultMode = specs.defaultMode; - *outAllowGroupSwitching = specs.allowGroupSwitching; - *outPrimaryRefreshRateMin = specs.primaryRefreshRateMin; - *outPrimaryRefreshRateMax = specs.primaryRefreshRateMax; - *outAppRequestRefreshRateMin = specs.appRequestRefreshRateMin; - *outAppRequestRefreshRateMax = specs.appRequestRefreshRateMax; - } + outSpecs); return statusTFromBinderStatus(status); } diff --git a/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl index fb4fcdf8e8..af138c7539 100644 --- a/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl +++ b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl @@ -18,10 +18,58 @@ package android.gui; /** @hide */ parcelable DisplayModeSpecs { + /** + * Defines the refresh rates ranges that should be used by SF. + */ + parcelable RefreshRateRanges { + /** + * Defines a range of refresh rates. + */ + parcelable RefreshRateRange { + float min; + float max; + } + + /** + * The range of refresh rates that the display should run at. + */ + RefreshRateRange physical; + + /** + * The range of refresh rates that apps should render at. + */ + RefreshRateRange render; + } + + /** + * Base mode ID. This is what system defaults to for all other settings, or + * if the refresh rate range is not available. + */ int defaultMode; + + /** + * If true this will allow switching between modes in different display configuration + * groups. This way the user may see visual interruptions when the display mode changes. + */ + boolean allowGroupSwitching; - float primaryRefreshRateMin; - float primaryRefreshRateMax; - float appRequestRefreshRateMin; - float appRequestRefreshRateMax; + + /** + * The primary physical and render refresh rate ranges represent DisplayManager's general + * guidance on the display modes SurfaceFlinger will consider when switching refresh + * rates and scheduling the frame rate. Unless SurfaceFlinger has a specific reason to do + * otherwise, it will stay within this range. + */ + RefreshRateRanges primaryRanges; + + /** + * The app request physical and render refresh rate ranges allow SurfaceFlinger to consider + * more display modes when switching refresh rates. Although SurfaceFlinger will + * generally stay within the primary range, specific considerations, such as layer frame + * rate settings specified via the setFrameRate() API, may cause SurfaceFlinger to go + * outside the primary range. SurfaceFlinger never goes outside the app request range. + * The app request range will be greater than or equal to the primary refresh rate range, + * never smaller. + */ + RefreshRateRanges appRequestRanges; } diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index 92d9e7799c..40410fb59e 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -327,25 +327,9 @@ interface ISurfaceComposer { /** * Sets the refresh rate boundaries for the display. * - * The primary refresh rate range represents display manager's general guidance on the display - * modes we'll consider when switching refresh rates. Unless we get an explicit signal from an - * app, we should stay within this range. - * - * The app request refresh rate range allows us to consider more display modes when switching - * refresh rates. Although we should generally stay within the primary range, specific - * considerations, such as layer frame rate settings specified via the setFrameRate() api, may - * cause us to go outside the primary range. We never go outside the app request range. The app - * request range will be greater than or equal to the primary refresh rate range, never smaller. - * - * defaultMode is used to narrow the list of display modes SurfaceFlinger will consider - * switching between. Only modes with a mode group and resolution matching defaultMode - * will be considered for switching. The defaultMode corresponds to an ID of mode in the list - * of supported modes returned from getDynamicDisplayInfo(). - */ - void setDesiredDisplayModeSpecs( - IBinder displayToken, int defaultMode, - boolean allowGroupSwitching, float primaryRefreshRateMin, float primaryRefreshRateMax, - float appRequestRefreshRateMin, float appRequestRefreshRateMax); + * @see DisplayModeSpecs.aidl for details. + */ + void setDesiredDisplayModeSpecs(IBinder displayToken, in DisplayModeSpecs specs); DisplayModeSpecs getDesiredDisplayModeSpecs(IBinder displayToken); diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h index 202517067f..9d1ee8f65b 100644 --- a/libs/gui/fuzzer/libgui_fuzzer_utils.h +++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h @@ -127,9 +127,7 @@ public: MOCK_METHOD(binder::Status, removeTunnelModeEnabledListener, (const sp<gui::ITunnelModeEnabledListener>&), (override)); MOCK_METHOD(binder::Status, setDesiredDisplayModeSpecs, - (const sp<IBinder>&, int32_t, bool, float, float, float, - float appRequestRefreshRateMax), - (override)); + (const sp<IBinder>&, const gui::DisplayModeSpecs&), (override)); MOCK_METHOD(binder::Status, getDesiredDisplayModeSpecs, (const sp<IBinder>&, gui::DisplayModeSpecs*), (override)); MOCK_METHOD(binder::Status, getDisplayBrightnessSupport, (const sp<IBinder>&, bool*), diff --git a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp index eecbe0fe21..57720dd513 100644 --- a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp +++ b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp @@ -123,10 +123,37 @@ private: sp<SurfaceControl> makeSurfaceControl(); BlurRegion getBlurRegion(); void fuzzOnPullAtom(); + gui::DisplayModeSpecs getDisplayModeSpecs(); FuzzedDataProvider mFdp; }; +gui::DisplayModeSpecs SurfaceComposerClientFuzzer::getDisplayModeSpecs() { + const auto getRefreshRateRange = [&] { + gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange range; + range.min = mFdp.ConsumeFloatingPoint<float>(); + range.max = mFdp.ConsumeFloatingPoint<float>(); + return range; + }; + + const auto getRefreshRateRanges = [&] { + gui::DisplayModeSpecs::RefreshRateRanges ranges; + ranges.physical = getRefreshRateRange(); + ranges.render = getRefreshRateRange(); + return ranges; + }; + + String8 displayName((mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes)).c_str()); + sp<IBinder> displayToken = + SurfaceComposerClient::createDisplay(displayName, mFdp.ConsumeBool() /*secure*/); + gui::DisplayModeSpecs specs; + specs.defaultMode = mFdp.ConsumeIntegral<int32_t>(); + specs.allowGroupSwitching = mFdp.ConsumeBool(); + specs.primaryRanges = getRefreshRateRanges(); + specs.appRequestRanges = getRefreshRateRanges(); + return specs; +} + BlurRegion SurfaceComposerClientFuzzer::getBlurRegion() { int32_t left = mFdp.ConsumeIntegral<int32_t>(); int32_t right = mFdp.ConsumeIntegral<int32_t>(); @@ -247,12 +274,7 @@ void SurfaceComposerClientFuzzer::invokeSurfaceComposerClient() { String8 displayName((mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes)).c_str()); sp<IBinder> displayToken = SurfaceComposerClient::createDisplay(displayName, mFdp.ConsumeBool() /*secure*/); - SurfaceComposerClient::setDesiredDisplayModeSpecs(displayToken, mFdp.ConsumeIntegral<int32_t>(), - mFdp.ConsumeBool() /*allowGroupSwitching*/, - mFdp.ConsumeFloatingPoint<float>(), - mFdp.ConsumeFloatingPoint<float>(), - mFdp.ConsumeFloatingPoint<float>(), - mFdp.ConsumeFloatingPoint<float>()); + SurfaceComposerClient::setDesiredDisplayModeSpecs(displayToken, getDisplayModeSpecs()); ui::ColorMode colorMode = mFdp.PickValueInArray(kColormodes); SurfaceComposerClient::setActiveColorMode(displayToken, colorMode); diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index c450e85857..2038f1477a 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -159,18 +159,11 @@ public: static status_t getActiveDisplayMode(const sp<IBinder>& display, ui::DisplayMode*); // Sets the refresh rate boundaries for the display. - static status_t setDesiredDisplayModeSpecs( - const sp<IBinder>& displayToken, ui::DisplayModeId defaultMode, - bool allowGroupSwitching, float primaryRefreshRateMin, float primaryRefreshRateMax, - float appRequestRefreshRateMin, float appRequestRefreshRateMax); + static status_t setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, + const gui::DisplayModeSpecs&); // Gets the refresh rate boundaries for the display. static status_t getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, - ui::DisplayModeId* outDefaultMode, - bool* outAllowGroupSwitching, - float* outPrimaryRefreshRateMin, - float* outPrimaryRefreshRateMax, - float* outAppRequestRefreshRateMin, - float* outAppRequestRefreshRateMax); + gui::DisplayModeSpecs*); // Get the coordinates of the display's native color primaries static status_t getDisplayNativePrimaries(const sp<IBinder>& display, diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index 346b686466..67c669ddb7 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -920,16 +920,12 @@ public: } binder::Status setDesiredDisplayModeSpecs(const sp<IBinder>& /*displayToken*/, - int32_t /*defaultMode*/, bool /*allowGroupSwitching*/, - float /*primaryRefreshRateMin*/, - float /*primaryRefreshRateMax*/, - float /*appRequestRefreshRateMin*/, - float /*appRequestRefreshRateMax*/) override { + const gui::DisplayModeSpecs&) override { return binder::Status::ok(); } binder::Status getDesiredDisplayModeSpecs(const sp<IBinder>& /*displayToken*/, - gui::DisplayModeSpecs* /*outSpecs*/) override { + gui::DisplayModeSpecs*) override { return binder::Status::ok(); } diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp index 3ab2ba898e..0375915839 100644 --- a/libs/jpegrecoverymap/Android.bp +++ b/libs/jpegrecoverymap/Android.bp @@ -23,16 +23,19 @@ package { cc_library_static { name: "libjpegrecoverymap", - vendor_available: true, + host_supported: true, export_include_dirs: ["include"], local_include_dirs: ["include"], srcs: [ "recoverymap.cpp", + "recoverymapmath.cpp", ], shared_libs: [ + "libimage_io", + "libjpeg", "libutils", ], } @@ -63,4 +66,4 @@ cc_library_static { srcs: [ "jpegdecoder.cpp", ], -}
\ No newline at end of file +} diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h index 2ab75503a5..df24b10ebc 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h @@ -14,6 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +#ifndef ANDROID_JPEGRECOVERYMAP_JPEGDECODER_H +#define ANDROID_JPEGRECOVERYMAP_JPEGDECODER_H + // We must include cstdio before jpeglib.h. It is a requirement of libjpeg. #include <cstdio> extern "C" { @@ -41,12 +45,22 @@ public: * Returns the decompressed raw image buffer pointer. This method must be called only after * calling decompressImage(). */ - const void* getDecompressedImagePtr(); + void* getDecompressedImagePtr(); /* * Returns the decompressed raw image buffer size. This method must be called only after * calling decompressImage(). */ size_t getDecompressedImageSize(); + /* + * Returns the image width in pixels. This method must be called only after calling + * decompressImage(). + */ + size_t getDecompressedImageWidth(); + /* + * Returns the image width in pixels. This method must be called only after calling + * decompressImage(). + */ + size_t getDecompressedImageHeight(); private: bool decode(const void* image, int length); // Returns false if errors occur. @@ -56,7 +70,12 @@ private: // Process 16 lines of Y and 16 lines of U/V each time. // We must pass at least 16 scanlines according to libjpeg documentation. static const int kCompressBatchSize = 16; - // The buffer that holds the compressed result. + // The buffer that holds the decompressed result. std::vector<JOCTET> mResultBuffer; + // Resolution of the decompressed image. + size_t mWidth; + size_t mHeight; }; } /* namespace android */ + +#endif // ANDROID_JPEGRECOVERYMAP_JPEGDECODER_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h index 9641fda24c..61aeb8ace7 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h @@ -14,6 +14,9 @@ * limitations under the License. */ +#ifndef ANDROID_JPEGRECOVERYMAP_JPEGENCODER_H +#define ANDROID_JPEGRECOVERYMAP_JPEGENCODER_H + // We must include cstdio before jpeglib.h. It is a requirement of libjpeg. #include <cstdio> @@ -50,7 +53,7 @@ public: * Returns the compressed JPEG buffer pointer. This method must be called only after calling * compressImage(). */ - const void* getCompressedImagePtr(); + void* getCompressedImagePtr(); /* * Returns the compressed JPEG buffer size. This method must be called only after calling @@ -87,4 +90,6 @@ private: std::vector<JOCTET> mResultBuffer; }; -} /* namespace android */
\ No newline at end of file +} /* namespace android */ + +#endif // ANDROID_JPEGRECOVERYMAP_JPEGENCODER_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h index 49ab34d154..194cd2f403 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h @@ -35,6 +35,8 @@ enum { ERROR_JPEGR_INVALID_INPUT_TYPE = JPEGR_IO_ERROR_BASE, ERROR_JPEGR_INVALID_OUTPUT_TYPE = JPEGR_IO_ERROR_BASE - 1, ERROR_JPEGR_INVALID_NULL_PTR = JPEGR_IO_ERROR_BASE - 2, + ERROR_JPEGR_RESOLUTION_MISMATCH = JPEGR_IO_ERROR_BASE - 3, + ERROR_JPEGR_BUFFER_TOO_SMALL = JPEGR_IO_ERROR_BASE - 4, JPEGR_RUNTIME_ERROR_BASE = -20000, ERROR_JPEGR_ENCODE_ERROR = JPEGR_RUNTIME_ERROR_BASE - 1, @@ -43,4 +45,4 @@ enum { ERROR_JPEGR_METADATA_ERROR = JPEGR_RUNTIME_ERROR_BASE - 4, }; -} // namespace android::recoverymap
\ No newline at end of file +} // namespace android::recoverymap diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h index 31f1872491..b2ca481aa7 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h @@ -14,10 +14,20 @@ * limitations under the License. */ +#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAP_H +#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAP_H + #include "jpegrerrorcode.h" namespace android::recoverymap { +typedef enum { + JPEGR_COLORSPACE_UNSPECIFIED, + JPEGR_COLORSPACE_BT709, + JPEGR_COLORSPACE_P3, + JPEGR_COLORSPACE_BT2100, +} jpegr_color_space; + /* * Holds information for uncompressed image or recovery map. */ @@ -28,6 +38,8 @@ struct jpegr_uncompressed_struct { int width; // Height of the recovery map or image in pixels. int height; + // Color space. + jpegr_color_space colorSpace; }; /* @@ -36,12 +48,25 @@ struct jpegr_uncompressed_struct { struct jpegr_compressed_struct { // Pointer to the data location. void* data; + // Data length. + int length; + // Color space. + jpegr_color_space colorSpace; +}; + +/* + * Holds information for EXIF metadata. + */ +struct jpegr_exif_struct { + // Pointer to the data location. + void* data; // Data length; int length; }; typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr; typedef struct jpegr_compressed_struct* jr_compressed_ptr; +typedef struct jpegr_exif_struct* jr_exif_ptr; class RecoveryMap { public: @@ -49,21 +74,29 @@ public: * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV. * * Generate recovery map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append - * the recovery map to the end of the compressed JPEG. + * the recovery map to the end of the compressed JPEG. HDR and SDR inputs must be the same + * resolution and color space. * @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 dest destination of the compressed JPEGR image + * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is + * the highest quality + * @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, - void* dest); + jr_compressed_ptr dest, + int quality, + jr_exif_ptr exif); /* - * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV. + * Compress JPEGR image from 10-bit HDR YUV, 8-bit SDR YUV and compressed 8-bit JPEG. + * + * This method requires HAL Hardware JPEG encoder. * * Generate recovery map from the HDR and SDR inputs, append the recovery map to the end of the - * compressed JPEG. + * compressed JPEG. HDR and SDR inputs must be the same resolution and color space. * @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 compressed_jpeg_image compressed 8-bit JPEG image @@ -72,31 +105,48 @@ public: */ status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, - void* compressed_jpeg_image, - void* dest); + jr_compressed_ptr compressed_jpeg_image, + jr_compressed_ptr dest); /* * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV. * + * This method requires HAL Hardware JPEG encoder. + * * Decode the compressed 8-bit JPEG image to YUV SDR, generate recovery map from the HDR input - * and the decoded SDR result, append the recovery map to the end of the compressed JPEG. + * and the decoded SDR result, append the recovery map to the end of the compressed JPEG. HDR + * and SDR inputs must be the same resolution and color space. * @param uncompressed_p010_image uncompressed HDR image in P010 color format * @param compressed_jpeg_image compressed 8-bit JPEG image * @param dest destination of the compressed JPEGR image * @return NO_ERROR if encoding succeeds, error code if error occurs. */ status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, - void* compressed_jpeg_image, - void* dest); + jr_compressed_ptr compressed_jpeg_image, + jr_compressed_ptr dest); /* * Decompress JPEGR image. * * @param compressed_jpegr_image compressed JPEGR image * @param dest destination of the uncompressed JPEGR image + * @param exif destination of the decoded EXIF metadata. Default value is nullptr where EXIF + * metadata will not be decoded. + * @param request_sdr flag that request SDR output, default to false (request HDR output). If + * set to true, decoder will only decode the primary image which is SDR. + * Setting of request_sdr and input source (HDR or SDR) can be found in + * the table below: + * | input source | request_sdr | output of decoding | + * | HDR | true | SDR | + * | HDR | false | HDR | + * | SDR | true | SDR | + * | SDR | false | SDR | * @return NO_ERROR if decoding succeeds, error code if error occurs. */ - status_t decodeJPEGR(void* compressed_jpegr_image, jr_uncompressed_ptr dest); + status_t decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, + jr_uncompressed_ptr dest, + jr_exif_ptr exif = nullptr, + bool request_sdr = false); private: /* * This method is called in the decoding pipeline. It will decode the recovery map. @@ -105,7 +155,7 @@ private: * @param dest decoded recover map * @return NO_ERROR if decoding succeeds, error code if error occurs. */ - status_t decodeRecoveryMap(jr_compressed_ptr compressed_recovery_map, + status_t decompressRecoveryMap(jr_compressed_ptr compressed_recovery_map, jr_uncompressed_ptr dest); /* @@ -115,21 +165,24 @@ private: * @param dest encoded recover map * @return NO_ERROR if encoding succeeds, error code if error occurs. */ - status_t encodeRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map, + status_t compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map, jr_compressed_ptr dest); /* * 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 recovery map. + * 10-bit yuv images as input, and calculate the uncompressed recovery map. The input images + * must be the same resolution. * * @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 dest recover map + * @param dest recovery map; caller responsible for memory of data + * @param hdr_ratio HDR ratio will be updated in this method * @return NO_ERROR if calculation succeeds, error code if error occurs. */ status_t generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, jr_uncompressed_ptr uncompressed_p010_image, - jr_uncompressed_ptr dest); + jr_uncompressed_ptr dest, + float &hdr_ratio); /* * This method is called in the decoding pipeline. It will take the uncompressed (decoded) @@ -153,7 +206,7 @@ private: * @param dest destination of compressed recovery map * @return NO_ERROR if calculation succeeds, error code if error occurs. */ - status_t extractRecoveryMap(void* compressed_jpegr_image, jr_compressed_ptr dest); + status_t extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image, jr_compressed_ptr dest); /* * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image @@ -162,12 +215,57 @@ private: * * @param compressed_jpeg_image compressed 8-bit JPEG image * @param compress_recovery_map compressed recover map + * @param hdr_ratio HDR ratio * @param dest compressed JPEGR image * @return NO_ERROR if calculation succeeds, error code if error occurs. */ - status_t appendRecoveryMap(void* compressed_jpeg_image, + status_t appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, jr_compressed_ptr compressed_recovery_map, - void* dest); + float hdr_ratio, + jr_compressed_ptr dest); + + /* + * This method generates XMP metadata. + * + * below is an example of the XMP metadata that this function generates where + * secondary_image_length = 1000 + * hdr_ratio = 1.25 + * + * <x:xmpmeta + * xmlns:x="adobe:ns:meta/" + * x:xmptk="Adobe XMP Core 5.1.2"> + * <rdf:RDF + * xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> + * <rdf:Description + * xmlns:GContainer="http://ns.google.com/photos/1.0/container/"> + * <GContainer:Version>1</GContainer:Version> + * <GContainer:HdrRatio>1.25</GContainer:HdrRatio> + * <GContainer:Directory> + * <rdf:Seq> + * <rdf:li> + * <GContainer:Item + * Item:Semantic="Primary" + * Item:Mime="image/jpeg"/> + * </rdf:li> + * <rdf:li> + * <GContainer:Item + * Item:Semantic="RecoveryMap" + * Item:Mime="image/jpeg" + * Item:Length="1000"/> + * </rdf:li> + * </rdf:Seq> + * </GContainer:Directory> + * </rdf:Description> + * </rdf:RDF> + * </x:xmpmeta> + * + * @param secondary_image_length length of secondary image + * @param hdr_ratio hdr ratio + * @return XMP metadata in type of string + */ + std::string generateXmp(int secondary_image_length, float hdr_ratio); }; } // namespace android::recoverymap + +#endif // ANDROID_JPEGRECOVERYMAP_RECOVERYMAP_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h new file mode 100644 index 0000000000..8e9b07bf3d --- /dev/null +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h @@ -0,0 +1,106 @@ +/* + * 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. + */ + +#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H +#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H + +#include <stdint.h> + +#include <jpegrecoverymap/recoverymap.h> + +namespace android::recoverymap { + +const float kSdrWhiteNits = 100.0f; + +struct Color { + union { + struct { + float r; + float g; + float b; + }; + struct { + float y; + float u; + float v; + }; + }; +}; + +/* + * Convert from OETF'd bt.2100 RGB to YUV, according to BT.2100 + */ +Color bt2100RgbToYuv(Color e); + +/* + * Convert srgb YUV to RGB, according to ECMA TR/98. + */ +Color srgbYuvToRgb(Color e); + +/* + * TODO: better source for srgb transfer function + * Convert from srgb to linear, according to https://en.wikipedia.org/wiki/SRGB. + * [0.0, 1.0] range in and out. + */ +float srgbInvOetf(float e); +Color srgbInvOetf(Color e); + +/* + * Convert from HLG to scene luminance in nits, according to BT.2100. + */ +float hlgInvOetf(float e); + +/* + * Convert from scene luminance in nits to HLG, according to BT.2100. + */ +float hlgOetf(float e); +Color hlgOetf(Color e); + +/* + * Calculate the 8-bit unsigned integer recovery value for the given SDR and HDR + * luminances in linear space, and the hdr ratio to encode against. + */ +uint8_t encodeRecovery(float y_sdr, float y_hdr, float hdr_ratio); + +/* + * Calculates the linear luminance in nits after applying the given recovery + * value, with the given hdr ratio, to the given sdr input in the range [0, 1]. + */ +Color applyRecovery(Color e, float recovery, float hdr_ratio); + +/* + * Helper for sampling from images. + */ +Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y); + +/* + * Sample the recovery value for the map from a given x,y coordinate on a scale + * that is map scale factor larger than the map size. + */ +float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y); + +/* + * Sample the image Y value at the provided location, with a weighting based on nearby pixels + * and the map scale factor. + * + * Expect narrow-range image data for P010. + */ +float sampleYuv420Y(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y); +float sampleP010Y(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y); + +} // namespace android::recoverymap + +#endif // ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoder.cpp index 22a5389648..c1fb6c3f1d 100644 --- a/libs/jpegrecoverymap/jpegdecoder.cpp +++ b/libs/jpegrecoverymap/jpegdecoder.cpp @@ -95,7 +95,7 @@ bool JpegDecoder::decompressImage(const void* image, int length) { return true; } -const void* JpegDecoder::getDecompressedImagePtr() { +void* JpegDecoder::getDecompressedImagePtr() { return mResultBuffer.data(); } @@ -103,6 +103,14 @@ size_t JpegDecoder::getDecompressedImageSize() { return mResultBuffer.size(); } +size_t JpegDecoder::getDecompressedImageWidth() { + return mWidth; +} + +size_t JpegDecoder::getDecompressedImageHeight() { + return mHeight; +} + bool JpegDecoder::decode(const void* image, int length) { jpeg_decompress_struct cinfo; jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length); @@ -119,6 +127,9 @@ bool JpegDecoder::decode(const void* image, int length) { cinfo.src = &mgr; jpeg_read_header(&cinfo, TRUE); + mWidth = cinfo.image_width; + mHeight = cinfo.image_height; + if (cinfo.jpeg_color_space == JCS_YCbCr) { mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0); } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { @@ -222,4 +233,4 @@ bool JpegDecoder::decompressSingleChannel(jpeg_decompress_struct* cinfo, const u return true; } -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/libs/jpegrecoverymap/jpegencoder.cpp b/libs/jpegrecoverymap/jpegencoder.cpp index d45d9b33c9..1997bf9ea3 100644 --- a/libs/jpegrecoverymap/jpegencoder.cpp +++ b/libs/jpegrecoverymap/jpegencoder.cpp @@ -52,7 +52,7 @@ bool JpegEncoder::compressImage(const void* image, int width, int height, int qu return true; } -const void* JpegEncoder::getCompressedImagePtr() { +void* JpegEncoder::getCompressedImagePtr() { return mResultBuffer.data(); } @@ -236,4 +236,4 @@ bool JpegEncoder::compressSingleChannel(jpeg_compress_struct* cinfo, const uint8 return true; } -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index 67c23e9788..bd16a68b0d 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -14,28 +14,124 @@ * limitations under the License. */ +// TODO: need to clean up handling around hdr_ratio and passing it around +// TODO: need to handle color space information; currently we assume everything +// is srgb in. +// TODO: handle PQ encode/decode (currently only HLG) + #include <jpegrecoverymap/recoverymap.h> +#include <jpegrecoverymap/jpegencoder.h> +#include <jpegrecoverymap/jpegdecoder.h> +#include <jpegrecoverymap/recoverymapmath.h> + +#include <image_io/jpeg/jpeg_marker.h> +#include <image_io/xml/xml_writer.h> + +#include <memory> +#include <sstream> +#include <string> + +using namespace std; namespace android::recoverymap { +#define JPEGR_CHECK(x) \ + { \ + status_t status = (x); \ + if ((status) != NO_ERROR) { \ + return status; \ + } \ + } + +// Map is quarter res / sixteenth size +static const size_t kMapDimensionScaleFactor = 4; + + +/* + * Helper function used for generating XMP metadata. + * + * @param prefix The prefix part of the name. + * @param suffix The suffix part of the name. + * @return A name of the form "prefix:suffix". + */ +string Name(const string &prefix, const string &suffix) { + std::stringstream ss; + ss << prefix << ":" << suffix; + return ss.str(); +} + +/* + * Helper function used for writing data to destination. + * + * @param destination destination of the data to be written. + * @param source source of data being written. + * @param length length of the data to be written. + * @param position cursor in desitination where the data is to be written. + * @return status of succeed or error code. + */ +status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) { + if (position + length > destination->length) { + return ERROR_JPEGR_BUFFER_TOO_SMALL; + } + + memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length); + position += length; + return NO_ERROR; +} + status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, - void* dest) { + jr_compressed_ptr dest, + int quality, + jr_exif_ptr /* exif */) { if (uncompressed_p010_image == nullptr || uncompressed_yuv_420_image == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } - // TBD + if (quality < 0 || quality > 100) { + 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) { + return ERROR_JPEGR_RESOLUTION_MISMATCH; + } + + jpegr_uncompressed_struct map; + float hdr_ratio = 0.0f; + JPEGR_CHECK(generateRecoveryMap( + uncompressed_yuv_420_image, uncompressed_p010_image, &map, hdr_ratio)); + std::unique_ptr<uint8_t[]> map_data; + map_data.reset(reinterpret_cast<uint8_t*>(map.data)); + + jpegr_compressed_struct compressed_map; + std::unique_ptr<uint8_t[]> compressed_map_data = + std::make_unique<uint8_t[]>(map.width * map.height); + compressed_map.data = compressed_map_data.get(); + JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); + + JpegEncoder jpeg_encoder; + // TODO: ICC data - need color space information + if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data, + uncompressed_yuv_420_image->width, + uncompressed_yuv_420_image->height, quality, nullptr, 0)) { + return ERROR_JPEGR_ENCODE_ERROR; + } + jpegr_compressed_struct jpeg; + jpeg.data = jpeg_encoder.getCompressedImagePtr(); + jpeg.length = jpeg_encoder.getCompressedImageSize(); + + JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, hdr_ratio, dest)); + return NO_ERROR; } status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jr_uncompressed_ptr uncompressed_yuv_420_image, - void* compressed_jpeg_image, - void* dest) { - + jr_compressed_ptr compressed_jpeg_image, + jr_compressed_ptr dest) { if (uncompressed_p010_image == nullptr || uncompressed_yuv_420_image == nullptr || compressed_jpeg_image == nullptr @@ -43,62 +139,197 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return ERROR_JPEGR_INVALID_NULL_PTR; } - // TBD + if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width + || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) { + return ERROR_JPEGR_RESOLUTION_MISMATCH; + } + + jpegr_uncompressed_struct map; + float hdr_ratio = 0.0f; + JPEGR_CHECK(generateRecoveryMap( + uncompressed_yuv_420_image, uncompressed_p010_image, &map, hdr_ratio)); + std::unique_ptr<uint8_t[]> map_data; + map_data.reset(reinterpret_cast<uint8_t*>(map.data)); + + jpegr_compressed_struct compressed_map; + std::unique_ptr<uint8_t[]> compressed_map_data = + std::make_unique<uint8_t[]>(map.width * map.height); + compressed_map.data = compressed_map_data.get(); + JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); + + JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, hdr_ratio, dest)); + return NO_ERROR; } status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, - void* compressed_jpeg_image, - void* dest) { + jr_compressed_ptr compressed_jpeg_image, + jr_compressed_ptr dest) { if (uncompressed_p010_image == nullptr || compressed_jpeg_image == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } - // TBD + JpegDecoder jpeg_decoder; + if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->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(); + + if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width + || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) { + return ERROR_JPEGR_RESOLUTION_MISMATCH; + } + + jpegr_uncompressed_struct map; + float hdr_ratio = 0.0f; + JPEGR_CHECK(generateRecoveryMap( + &uncompressed_yuv_420_image, uncompressed_p010_image, &map, hdr_ratio)); + std::unique_ptr<uint8_t[]> map_data; + map_data.reset(reinterpret_cast<uint8_t*>(map.data)); + + jpegr_compressed_struct compressed_map; + std::unique_ptr<uint8_t[]> compressed_map_data = + std::make_unique<uint8_t[]>(map.width * map.height); + compressed_map.data = compressed_map_data.get(); + JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); + + JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, hdr_ratio, dest)); + return NO_ERROR; } -status_t RecoveryMap::decodeJPEGR(void* compressed_jpegr_image, jr_uncompressed_ptr dest) { +status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, + jr_uncompressed_ptr dest, + jr_exif_ptr /* exif */, + bool /* request_sdr */) { if (compressed_jpegr_image == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } - // TBD + jpegr_compressed_struct compressed_map; + JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map)); + + jpegr_uncompressed_struct map; + JPEGR_CHECK(decompressRecoveryMap(&compressed_map, &map)); + + JpegDecoder jpeg_decoder; + if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->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(); + + JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, dest)); + return NO_ERROR; } -status_t RecoveryMap::decodeRecoveryMap(jr_compressed_ptr compressed_recovery_map, - jr_uncompressed_ptr dest) { +status_t RecoveryMap::decompressRecoveryMap(jr_compressed_ptr compressed_recovery_map, + jr_uncompressed_ptr dest) { if (compressed_recovery_map == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } - // TBD + JpegDecoder jpeg_decoder; + if (!jpeg_decoder.decompressImage(compressed_recovery_map->data, + compressed_recovery_map->length)) { + return ERROR_JPEGR_DECODE_ERROR; + } + + dest->data = jpeg_decoder.getDecompressedImagePtr(); + dest->width = jpeg_decoder.getDecompressedImageWidth(); + dest->height = jpeg_decoder.getDecompressedImageHeight(); + return NO_ERROR; } -status_t RecoveryMap::encodeRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map, - jr_compressed_ptr dest) { +status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map, + jr_compressed_ptr dest) { if (uncompressed_recovery_map == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } - // TBD + // TODO: should we have ICC data? + JpegEncoder jpeg_encoder; + if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data, uncompressed_recovery_map->width, + uncompressed_recovery_map->height, 85, nullptr, 0, + true /* isSingleChannel */)) { + return ERROR_JPEGR_ENCODE_ERROR; + } + + if (dest->length < jpeg_encoder.getCompressedImageSize()) { + return ERROR_JPEGR_BUFFER_TOO_SMALL; + } + + memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize()); + dest->length = jpeg_encoder.getCompressedImageSize(); + return NO_ERROR; } status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, jr_uncompressed_ptr uncompressed_p010_image, - jr_uncompressed_ptr dest) { + jr_uncompressed_ptr dest, + float &hdr_ratio) { if (uncompressed_yuv_420_image == nullptr || uncompressed_p010_image == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } - // TBD + if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width + || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) { + return ERROR_JPEGR_RESOLUTION_MISMATCH; + } + + size_t image_width = uncompressed_yuv_420_image->width; + size_t image_height = uncompressed_yuv_420_image->height; + size_t map_width = image_width / kMapDimensionScaleFactor; + size_t map_height = image_height / kMapDimensionScaleFactor; + + dest->width = map_width; + dest->height = map_height; + dest->data = new uint8_t[map_width * map_height]; + std::unique_ptr<uint8_t[]> map_data; + map_data.reset(reinterpret_cast<uint8_t*>(dest->data)); + + uint16_t yp_hdr_max = 0; + for (size_t y = 0; y < image_height; ++y) { + for (size_t x = 0; x < image_width; ++x) { + size_t pixel_idx = x + y * image_width; + uint16_t yp_hdr = reinterpret_cast<uint8_t*>(uncompressed_yuv_420_image->data)[pixel_idx]; + if (yp_hdr > yp_hdr_max) { + yp_hdr_max = yp_hdr; + } + } + } + + float y_hdr_max_nits = hlgInvOetf(yp_hdr_max); + hdr_ratio = y_hdr_max_nits / kSdrWhiteNits; + + for (size_t y = 0; y < map_height; ++y) { + for (size_t x = 0; x < map_width; ++x) { + float yp_sdr = sampleYuv420Y(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y); + float yp_hdr = sampleP010Y(uncompressed_p010_image, kMapDimensionScaleFactor, x, y); + + float y_sdr_nits = srgbInvOetf(yp_sdr); + float y_hdr_nits = hlgInvOetf(yp_hdr); + + size_t pixel_idx = x + y * map_width; + reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] = + encodeRecovery(y_sdr_nits, y_hdr_nits, hdr_ratio); + } + } + + map_data.release(); return NO_ERROR; } @@ -111,11 +342,44 @@ status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ return ERROR_JPEGR_INVALID_NULL_PTR; } - // TBD + // TODO: need to get this from the XMP; should probably be a function + // parameter + float hdr_ratio = 4.0f; + + size_t width = uncompressed_yuv_420_image->width; + size_t height = uncompressed_yuv_420_image->height; + + dest->width = width; + dest->height = height; + size_t pixel_count = width * height; + + for (size_t y = 0; y < height; ++y) { + for (size_t x = 0; x < width; ++x) { + size_t pixel_y_idx = x + y * width; + + size_t pixel_uv_idx = x / 2 + (y / 2) * (width / 2); + + Color ypuv_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y); + Color rgbp_sdr = srgbYuvToRgb(ypuv_sdr); + Color rgb_sdr = srgbInvOetf(rgbp_sdr); + + float recovery = sampleMap(uncompressed_recovery_map, kMapDimensionScaleFactor, x, y); + Color rgb_hdr = applyRecovery(rgb_sdr, recovery, hdr_ratio); + + Color rgbp_hdr = hlgOetf(rgb_hdr); + Color ypuv_hdr = bt2100RgbToYuv(rgbp_hdr); + + reinterpret_cast<uint16_t*>(dest->data)[pixel_y_idx] = ypuv_hdr.r; + reinterpret_cast<uint16_t*>(dest->data)[pixel_count + pixel_uv_idx] = ypuv_hdr.g; + reinterpret_cast<uint16_t*>(dest->data)[pixel_count + pixel_uv_idx + 1] = ypuv_hdr.b; + } + } + return NO_ERROR; } -status_t RecoveryMap::extractRecoveryMap(void* compressed_jpegr_image, jr_compressed_ptr dest) { +status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image, + jr_compressed_ptr dest) { if (compressed_jpegr_image == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } @@ -124,17 +388,99 @@ status_t RecoveryMap::extractRecoveryMap(void* compressed_jpegr_image, jr_compre return NO_ERROR; } -status_t RecoveryMap::appendRecoveryMap(void* compressed_jpeg_image, - jr_compressed_ptr compressed_recovery_map, - void* dest) { +status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, + jr_compressed_ptr compressed_recovery_map, + float hdr_ratio, + jr_compressed_ptr dest) { if (compressed_jpeg_image == nullptr || compressed_recovery_map == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } - // TBD + string xmp = generateXmp(compressed_recovery_map->length, hdr_ratio); + string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; + + // 2 bytes: APP1 sign (ff e1) + // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0" + // x bytes: length of xmp packet + int length = 2 + nameSpace.size() + xmp.size(); + uint8_t lengthH = ((length >> 8) & 0xff); + uint8_t lengthL = (length & 0xff); + + int pos = 0; + + // JPEG/R structure: + // SOI (ff d8) + // APP1 (ff e1) + // 2 bytes of length (2 + 29 + length of xmp packet) + // name space ("http://ns.adobe.com/xap/1.0/\0") + // xmp + // primary image (without the first two bytes, the SOI sign) + // secondary image (the recovery map) + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos)); + 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, (void*)nameSpace.c_str(), nameSpace.size(), pos)); + JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos)); + JPEGR_CHECK(Write(dest, + (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos)); + JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos)); + dest->length = pos; + return NO_ERROR; } +string RecoveryMap::generateXmp(int secondary_image_length, float hdr_ratio) { + const string kContainerPrefix = "GContainer"; + const string kContainerUri = "http://ns.google.com/photos/1.0/container/"; + const string kItemPrefix = "Item"; + const string kRecoveryMap = "RecoveryMap"; + const string kDirectory = "Directory"; + const string kImageJpeg = "image/jpeg"; + const string kItem = "Item"; + const string kLength = "Length"; + const string kMime = "Mime"; + const string kPrimary = "Primary"; + const string kSemantic = "Semantic"; + const string kVersion = "Version"; + const int kVersionValue = 1; + + const string kConDir = Name(kContainerPrefix, kDirectory); + const string kContainerItem = Name(kContainerPrefix, kItem); + const string kItemLength = Name(kItemPrefix, kLength); + const string kItemMime = Name(kItemPrefix, kMime); + const string kItemSemantic = Name(kItemPrefix, kSemantic); + + const vector<string> kConDirSeq({kConDir, string("rdf:Seq")}); + const vector<string> kLiItem({string("rdf:li"), kContainerItem}); + + std::stringstream ss; + photos_editing_formats::image_io::XmlWriter writer(ss); + writer.StartWritingElement("x:xmpmeta"); + writer.WriteXmlns("x", "adobe:ns:meta/"); + writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2"); + writer.StartWritingElement("rdf:RDF"); + writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); + writer.StartWritingElement("rdf:Description"); + writer.WriteXmlns(kContainerPrefix, kContainerUri); + writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), kVersionValue); + writer.WriteElementAndContent(Name(kContainerPrefix, "HdrRatio"), hdr_ratio); + writer.StartWritingElements(kConDirSeq); + size_t item_depth = writer.StartWritingElements(kLiItem); + writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary); + writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg); + writer.FinishWritingElementsToDepth(item_depth); + writer.StartWritingElements(kLiItem); + writer.WriteAttributeNameAndValue(kItemSemantic, kRecoveryMap); + writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg); + writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length); + writer.FinishWriting(); + + return ss.str(); +} + } // namespace android::recoverymap diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp new file mode 100644 index 0000000000..3e110bcd26 --- /dev/null +++ b/libs/jpegrecoverymap/recoverymapmath.cpp @@ -0,0 +1,169 @@ +/* + * 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 <cmath> + +#include <jpegrecoverymap/recoverymapmath.h> + +namespace android::recoverymap { + +static const float kBt2100R = 0.2627f, kBt2100G = 0.6780f, kBt2100B = 0.0593f; +static const float kBt2100Cb = 1.8814f, kBt2100Cr = 1.4746f; + +Color bt2100RgbToYuv(Color e) { + float yp = kBt2100R * e.r + kBt2100G * e.g + kBt2100B * e.b; + return {{{yp, (e.b - yp) / kBt2100Cb, (e.r - yp) / kBt2100Cr }}}; +} + +static const float kSrgbRCr = 1.402f, kSrgbGCb = 0.34414f, kSrgbGCr = 0.71414f, kSrgbBCb = 1.772f; + +Color srgbYuvToRgb(Color e) { + return {{{ e.y + kSrgbRCr * e.v, e.y - kSrgbGCb * e.u - kSrgbGCr * e.v, e.y + kSrgbBCb * e.u }}}; +} + +float srgbInvOetf(float e) { + if (e <= 0.04045f) { + return e / 12.92f; + } else { + return pow((e + 0.055f) / 1.055f, 2.4); + } +} + +Color srgbInvOetf(Color e) { + return {{{ srgbInvOetf(e.r), srgbInvOetf(e.g), srgbInvOetf(e.b) }}}; +} + +static const float kHlgA = 0.17883277f, kHlgB = 0.28466892f, kHlgC = 0.55991073; + +float hlgInvOetf(float e) { + if (e <= 0.5f) { + return pow(e, 2.0f) / 3.0f; + } else { + return (exp((e - kHlgC) / kHlgA) + kHlgB) / 12.0f; + } +} + +float hlgOetf(float e) { + if (e <= 1.0f/12.0f) { + return sqrt(3.0f * e); + } else { + return kHlgA * log(12.0f * e - kHlgB) + kHlgC; + } +} + +Color hlgOetf(Color e) { + return {{{ hlgOetf(e.r), hlgOetf(e.g), hlgOetf(e.b) }}}; +} + +uint8_t EncodeRecovery(float y_sdr, float y_hdr, float hdr_ratio) { + float gain = 1.0f; + if (y_sdr > 0.0f) { + gain = y_hdr / y_sdr; + } + + if (gain < -hdr_ratio) gain = -hdr_ratio; + if (gain > hdr_ratio) gain = hdr_ratio; + + return static_cast<uint8_t>(log2(gain) / log2(hdr_ratio) * 127.5f + 127.5f); +} + +float applyRecovery(float y_sdr, float recovery, float hdr_ratio) { + return exp2(log2(y_sdr) + recovery * log2(hdr_ratio)); +} + +// TODO: do we need something more clever for filtering either the map or images +// to generate the map? + +static float mapUintToFloat(uint8_t map_uint) { + return (static_cast<float>(map_uint) - 127.5f) / 127.5f; +} + +float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y) { + float x_map = static_cast<float>(x) / static_cast<float>(map_scale_factor); + float y_map = static_cast<float>(y) / static_cast<float>(map_scale_factor); + + size_t x_lower = static_cast<size_t>(floor(x_map)); + size_t x_upper = x_lower + 1; + size_t y_lower = static_cast<size_t>(floor(y_map)); + size_t y_upper = y_lower + 1; + + float x_influence = x_map - static_cast<float>(x_lower); + float y_influence = y_map - static_cast<float>(y_lower); + + float e1 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_lower * map->width]); + float e2 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_upper * map->width]); + float e3 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_lower * map->width]); + float e4 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_upper * map->width]); + + return e1 * (x_influence + y_influence) / 2.0f + + e2 * (x_influence + 1.0f - y_influence) / 2.0f + + e3 * (1.0f - x_influence + y_influence) / 2.0f + + e4 * (1.0f - x_influence + 1.0f - y_influence) / 2.0f; +} + +Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) { + size_t pixel_count = image->width * image->height; + + size_t pixel_y_idx = x + y * image->width; + size_t pixel_uv_idx = x / 2 + (y / 2) * (image->width / 2); + + 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]; + + // 128 bias for UV given we are using jpeglib; see: + // https://github.com/kornelski/libjpeg/blob/master/structure.doc + return {{{ static_cast<float>(y_uint) / 255.0f, + (static_cast<float>(u_uint) - 128.0f) / 255.0f, + (static_cast<float>(v_uint) - 128.0f) / 255.0f }}}; +} + +typedef float (*sampleComponentFn)(jr_uncompressed_ptr, size_t, size_t); + +static float sampleComponent(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y, + sampleComponentFn sample_fn) { + float e = 0.0f; + for (size_t dy = 0; dy < map_scale_factor; ++dy) { + for (size_t dx = 0; dx < map_scale_factor; ++dx) { + e += sample_fn(image, x * map_scale_factor + dx, y * map_scale_factor + dy); + } + } + + return e / static_cast<float>(map_scale_factor * map_scale_factor); +} + +static float getYuv420Y(jr_uncompressed_ptr image, size_t x, size_t y) { + size_t pixel_idx = x + y * image->width; + uint8_t y_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_idx]; + return static_cast<float>(y_uint) / 255.0f; +} + + +float sampleYuv420Y(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) { + return sampleComponent(image, map_scale_factor, x, y, getYuv420Y); +} + +static float getP010Y(jr_uncompressed_ptr image, size_t x, size_t y) { + size_t pixel_idx = x + y * image->width; + uint8_t y_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_idx]; + // Expecting narrow range input + return (static_cast<float>(y_uint) - 64.0f) / 960.0f; +} + +float sampleP010Y(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) { + return sampleComponent(image, map_scale_factor, x, y, getP010Y); +} +} // namespace android::recoverymap diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp index 7f37f611c7..41af991093 100644 --- a/libs/jpegrecoverymap/tests/Android.bp +++ b/libs/jpegrecoverymap/tests/Android.bp @@ -62,4 +62,4 @@ cc_test { "libjpegdecoder", "libgtest", ], -}
\ No newline at end of file +} diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp index 9d9cb6b2bc..f1fc0a45ad 100644 --- a/libs/renderengine/RenderEngine.cpp +++ b/libs/renderengine/RenderEngine.cpp @@ -19,6 +19,7 @@ #include <cutils/properties.h> #include <log/log.h> #include "gl/GLESRenderEngine.h" +#include "renderengine/ExternalTexture.h" #include "threaded/RenderEngineThreaded.h" #include "skia/SkiaGLRenderEngine.h" @@ -70,10 +71,22 @@ ftl::Future<FenceResult> RenderEngine::drawLayers(const DisplaySettings& display 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)); return resultFuture; } +void RenderEngine::updateProtectedContext(const std::vector<LayerSettings>& layers, + const std::shared_ptr<ExternalTexture>& buffer) { + const bool needsProtectedContext = + (buffer && (buffer->getUsage() & GRALLOC_USAGE_PROTECTED)) || + std::any_of(layers.begin(), layers.end(), [](const LayerSettings& layer) { + const std::shared_ptr<ExternalTexture>& buffer = layer.source.buffer.buffer; + return buffer && (buffer->getUsage() & GRALLOC_USAGE_PROTECTED); + }); + useProtectedContext(needsProtectedContext); +} + } // namespace renderengine } // namespace android diff --git a/libs/renderengine/gl/GLESRenderEngine.h b/libs/renderengine/gl/GLESRenderEngine.h index 1ee5cbaa3d..1b3492154b 100644 --- a/libs/renderengine/gl/GLESRenderEngine.h +++ b/libs/renderengine/gl/GLESRenderEngine.h @@ -61,7 +61,7 @@ public: 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 override { return mInProtectedContext; } + bool isProtected() const { return mInProtectedContext; } bool supportsProtectedContent() const override; void useProtectedContext(bool useProtectedContext) override; void cleanupPostRender() override; diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h index 199392c160..9182febbe0 100644 --- a/libs/renderengine/include/renderengine/RenderEngine.h +++ b/libs/renderengine/include/renderengine/RenderEngine.h @@ -126,12 +126,8 @@ public: // ----- BEGIN NEW INTERFACE ----- // queries that are required to be thread safe - virtual bool isProtected() const = 0; virtual bool supportsProtectedContent() const = 0; - // Attempt to switch RenderEngine into and out of protectedContext mode - virtual void useProtectedContext(bool useProtectedContext) = 0; - // Notify RenderEngine of changes to the dimensions of the active display // so that it can configure its internal caches accordingly. virtual void onActiveDisplaySizeChanged(ui::Size size) = 0; @@ -238,6 +234,13 @@ protected: friend class RenderEngineTest_cleanupPostRender_cleansUpOnce_Test; const RenderEngineType mRenderEngineType; + // Update protectedContext mode depending on whether or not any layer has a protected buffer. + void updateProtectedContext(const std::vector<LayerSettings>&, + const std::shared_ptr<ExternalTexture>&); + + // Attempt to switch RenderEngine into and out of protectedContext mode + virtual void useProtectedContext(bool useProtectedContext) = 0; + virtual void drawLayersInternal( const std::shared_ptr<std::promise<FenceResult>>&& resultPromise, const DisplaySettings& display, const std::vector<LayerSettings>& layers, diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h index e7c5b8f0ab..1973c7d065 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.h +++ b/libs/renderengine/skia/SkiaRenderEngine.h @@ -68,7 +68,6 @@ public: std::future<void> primeCache() override final; void cleanupPostRender() override final; void cleanFramebufferCache() override final{ } - bool isProtected() const override final{ return mInProtectedContext; } bool supportsBackgroundBlur() override final { return mBlurFilter != nullptr; } @@ -102,6 +101,8 @@ protected: size_t getMaxViewportDims() const override final; GrDirectContext* getActiveGrContext(); + bool isProtected() const { return mInProtectedContext; } + // Implements PersistentCache as a way to monitor what SkSL shaders Skia has // cached. class SkSLCacheMonitor : public GrContextOptions::PersistentCache { diff --git a/libs/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp index 1a96289bc0..fe3a16d4bf 100644 --- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp +++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp @@ -17,8 +17,10 @@ #include <cutils/properties.h> #include <gmock/gmock.h> #include <gtest/gtest.h> +#include <hardware/gralloc.h> #include <renderengine/impl/ExternalTexture.h> #include <renderengine/mock/RenderEngine.h> +#include <ui/PixelFormat.h> #include "../threaded/RenderEngineThreaded.h" namespace android { @@ -95,18 +97,6 @@ TEST_F(RenderEngineThreadedTest, getMaxViewportDims_returns0) { ASSERT_EQ(dims, result); } -TEST_F(RenderEngineThreadedTest, isProtected_returnsFalse) { - EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(false)); - status_t result = mThreadedRE->isProtected(); - ASSERT_EQ(false, result); -} - -TEST_F(RenderEngineThreadedTest, isProtected_returnsTrue) { - EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(true)); - size_t result = mThreadedRE->isProtected(); - ASSERT_EQ(true, result); -} - TEST_F(RenderEngineThreadedTest, supportsProtectedContent_returnsFalse) { EXPECT_CALL(*mRenderEngine, supportsProtectedContent()).WillOnce(Return(false)); status_t result = mThreadedRE->supportsProtectedContent(); @@ -119,28 +109,6 @@ TEST_F(RenderEngineThreadedTest, supportsProtectedContent_returnsTrue) { ASSERT_EQ(true, result); } -TEST_F(RenderEngineThreadedTest, useProtectedContext) { - EXPECT_CALL(*mRenderEngine, useProtectedContext(true)); - auto& ipExpect = EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(false)); - EXPECT_CALL(*mRenderEngine, supportsProtectedContent()).WillOnce(Return(true)); - EXPECT_CALL(*mRenderEngine, isProtected()).After(ipExpect).WillOnce(Return(true)); - - mThreadedRE->useProtectedContext(true); - ASSERT_EQ(true, mThreadedRE->isProtected()); - - // call ANY synchronous function to ensure that useProtectedContext has completed. - mThreadedRE->getContextPriority(); - ASSERT_EQ(true, mThreadedRE->isProtected()); -} - -TEST_F(RenderEngineThreadedTest, useProtectedContext_quickReject) { - EXPECT_CALL(*mRenderEngine, useProtectedContext(false)).Times(0); - EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(false)); - mThreadedRE->useProtectedContext(false); - // call ANY synchronous function to ensure that useProtectedContext has completed. - mThreadedRE->getContextPriority(); -} - TEST_F(RenderEngineThreadedTest, PostRenderCleanup_skipped) { EXPECT_CALL(*mRenderEngine, canSkipPostRenderCleanup()).WillOnce(Return(true)); EXPECT_CALL(*mRenderEngine, cleanupPostRender()).Times(0); @@ -182,6 +150,68 @@ TEST_F(RenderEngineThreadedTest, drawLayers) { 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>&, const bool, + base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); }); + + ftl::Future<FenceResult> future = + mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence)); + ASSERT_TRUE(future.valid()); + auto result = future.get(); + ASSERT_TRUE(result.ok()); +} + +TEST_F(RenderEngineThreadedTest, drawLayers_protectedLayer) { + renderengine::DisplaySettings settings; + auto layerBuffer = sp<GraphicBuffer>::make(); + layerBuffer->usage |= GRALLOC_USAGE_PROTECTED; + renderengine::LayerSettings layer; + layer.source.buffer.buffer = std::make_shared< + renderengine::impl::ExternalTexture>(std::move(layerBuffer), *mRenderEngine, + renderengine::impl::ExternalTexture::Usage:: + READABLE); + std::vector<renderengine::LayerSettings> layers = {std::move(layer)}; + 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(true)); + 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>&, const bool, + base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); }); + + ftl::Future<FenceResult> future = + mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence)); + ASSERT_TRUE(future.valid()); + auto result = future.get(); + ASSERT_TRUE(result.ok()); +} + +TEST_F(RenderEngineThreadedTest, drawLayers_protectedOutputBuffer) { + renderengine::DisplaySettings settings; + std::vector<renderengine::LayerSettings> layers; + auto graphicBuffer = sp<GraphicBuffer>::make(); + graphicBuffer->usage |= GRALLOC_USAGE_PROTECTED; + std::shared_ptr<renderengine::ExternalTexture> buffer = std::make_shared< + renderengine::impl:: + ExternalTexture>(std::move(graphicBuffer), *mRenderEngine, + renderengine::impl::ExternalTexture::Usage::READABLE | + renderengine::impl::ExternalTexture::Usage::WRITEABLE); + + base::unique_fd bufferFence; + + EXPECT_CALL(*mRenderEngine, useProtectedContext(true)); EXPECT_CALL(*mRenderEngine, drawLayersInternal) .WillOnce([&](const std::shared_ptr<std::promise<FenceResult>>&& resultPromise, const renderengine::DisplaySettings&, diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp index b41e8432a9..8aa41b3e50 100644 --- a/libs/renderengine/threaded/RenderEngineThreaded.cpp +++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp @@ -90,7 +90,6 @@ void RenderEngineThreaded::threadMain(CreateInstanceFactory factory) NO_THREAD_S } mRenderEngine = factory(); - mIsProtected = mRenderEngine->isProtected(); pthread_setname_np(pthread_self(), mThreadName); @@ -255,41 +254,11 @@ size_t RenderEngineThreaded::getMaxViewportDims() const { return mRenderEngine->getMaxViewportDims(); } -bool RenderEngineThreaded::isProtected() const { - waitUntilInitialized(); - std::lock_guard lock(mThreadMutex); - return mIsProtected; -} - bool RenderEngineThreaded::supportsProtectedContent() const { waitUntilInitialized(); return mRenderEngine->supportsProtectedContent(); } -void RenderEngineThreaded::useProtectedContext(bool useProtectedContext) { - if (isProtected() == useProtectedContext || - (useProtectedContext && !supportsProtectedContent())) { - return; - } - - { - std::lock_guard lock(mThreadMutex); - mFunctionCalls.push([useProtectedContext, this](renderengine::RenderEngine& instance) { - ATRACE_NAME("REThreaded::useProtectedContext"); - instance.useProtectedContext(useProtectedContext); - if (instance.isProtected() != useProtectedContext) { - ALOGE("Failed to switch RenderEngine context."); - // reset the cached mIsProtected value to a good state, but this does not - // prevent other callers of this method and isProtected from reading the - // invalid cached value. - mIsProtected = instance.isProtected(); - } - }); - mIsProtected = useProtectedContext; - } - mCondition.notify_one(); -} - void RenderEngineThreaded::cleanupPostRender() { if (canSkipPostRenderCleanup()) { return; @@ -334,6 +303,7 @@ ftl::Future<FenceResult> RenderEngineThreaded::drawLayers( 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)); }); diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h index bf2ebea2a0..168e2d2b06 100644 --- a/libs/renderengine/threaded/RenderEngineThreaded.h +++ b/libs/renderengine/threaded/RenderEngineThreaded.h @@ -51,9 +51,7 @@ public: size_t getMaxTextureSize() const override; size_t getMaxViewportDims() const override; - bool isProtected() const override; bool supportsProtectedContent() const override; - void useProtectedContext(bool useProtectedContext) override; void cleanupPostRender() override; ftl::Future<FenceResult> drawLayers(const DisplaySettings& display, @@ -84,6 +82,9 @@ private: void waitUntilInitialized() const; static status_t setSchedFifo(bool enabled); + // No-op. This method is only called on leaf implementations of RenderEngine. + void useProtectedContext(bool) override {} + /* ------------------------------------------------------------------------ * Threading */ @@ -107,7 +108,6 @@ private: * Render Engine */ std::unique_ptr<renderengine::RenderEngine> mRenderEngine; - std::atomic<bool> mIsProtected = false; }; } // namespace threaded } // namespace renderengine diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index a53fcd763d..24168a12a6 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -55,6 +55,7 @@ filegroup { "mapper/accumulator/CursorButtonAccumulator.cpp", "mapper/accumulator/CursorScrollAccumulator.cpp", "mapper/accumulator/HidUsageAccumulator.cpp", + "mapper/accumulator/MultiTouchMotionAccumulator.cpp", "mapper/accumulator/SingleTouchMotionAccumulator.cpp", "mapper/accumulator/TouchButtonAccumulator.cpp", ], diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp index acba4f6513..8e757a5bf7 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp @@ -28,163 +28,6 @@ namespace android { // Maximum number of slots supported when using the slot-based Multitouch Protocol B. static constexpr size_t MAX_SLOTS = 32; -// --- MultiTouchMotionAccumulator --- - -MultiTouchMotionAccumulator::MultiTouchMotionAccumulator() - : mCurrentSlot(-1), - mUsingSlotsProtocol(false), - mHaveStylus(false) {} - -void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, size_t slotCount, - bool usingSlotsProtocol) { - mUsingSlotsProtocol = usingSlotsProtocol; - mHaveStylus = deviceContext.hasAbsoluteAxis(ABS_MT_TOOL_TYPE); - mSlots = std::vector<Slot>(slotCount); - - mCurrentSlot = -1; - if (mUsingSlotsProtocol) { - // Query the driver for the current slot index and use it as the initial slot - // before we start reading events from the device. It is possible that the - // current slot index will not be the same as it was when the first event was - // written into the evdev buffer, which means the input mapper could start - // out of sync with the initial state of the events in the evdev buffer. - // In the extremely unlikely case that this happens, the data from - // two slots will be confused until the next ABS_MT_SLOT event is received. - // This can cause the touch point to "jump", but at least there will be - // no stuck touches. - int32_t initialSlot; - if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot); - status == OK) { - mCurrentSlot = initialSlot; - } else { - ALOGD("Could not retrieve current multi-touch slot index. status=%d", status); - } - } -} - -void MultiTouchMotionAccumulator::resetSlots() { - for (Slot& slot : mSlots) { - slot.clear(); - } - mCurrentSlot = -1; -} - -void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { - if (rawEvent->type == EV_ABS) { - bool newSlot = false; - if (mUsingSlotsProtocol) { - if (rawEvent->code == ABS_MT_SLOT) { - mCurrentSlot = rawEvent->value; - newSlot = true; - } - } else if (mCurrentSlot < 0) { - mCurrentSlot = 0; - } - - if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlots.size()) { - if (DEBUG_POINTERS) { - if (newSlot) { - ALOGW("MultiTouch device emitted invalid slot index %d but it " - "should be between 0 and %zd; ignoring this slot.", - mCurrentSlot, mSlots.size() - 1); - } - } - } else { - Slot& slot = mSlots[mCurrentSlot]; - // If mUsingSlotsProtocol is true, it means the raw pointer has axis info of - // ABS_MT_TRACKING_ID and ABS_MT_SLOT, so driver should send a valid trackingId while - // updating the slot. - if (!mUsingSlotsProtocol) { - slot.mInUse = true; - } - - switch (rawEvent->code) { - case ABS_MT_POSITION_X: - slot.mAbsMTPositionX = rawEvent->value; - warnIfNotInUse(*rawEvent, slot); - break; - case ABS_MT_POSITION_Y: - slot.mAbsMTPositionY = rawEvent->value; - warnIfNotInUse(*rawEvent, slot); - break; - case ABS_MT_TOUCH_MAJOR: - slot.mAbsMTTouchMajor = rawEvent->value; - break; - case ABS_MT_TOUCH_MINOR: - slot.mAbsMTTouchMinor = rawEvent->value; - slot.mHaveAbsMTTouchMinor = true; - break; - case ABS_MT_WIDTH_MAJOR: - slot.mAbsMTWidthMajor = rawEvent->value; - break; - case ABS_MT_WIDTH_MINOR: - slot.mAbsMTWidthMinor = rawEvent->value; - slot.mHaveAbsMTWidthMinor = true; - break; - case ABS_MT_ORIENTATION: - slot.mAbsMTOrientation = rawEvent->value; - break; - case ABS_MT_TRACKING_ID: - if (mUsingSlotsProtocol && rawEvent->value < 0) { - // The slot is no longer in use but it retains its previous contents, - // which may be reused for subsequent touches. - slot.mInUse = false; - } else { - slot.mInUse = true; - slot.mAbsMTTrackingId = rawEvent->value; - } - break; - case ABS_MT_PRESSURE: - slot.mAbsMTPressure = rawEvent->value; - break; - case ABS_MT_DISTANCE: - slot.mAbsMTDistance = rawEvent->value; - break; - case ABS_MT_TOOL_TYPE: - slot.mAbsMTToolType = rawEvent->value; - slot.mHaveAbsMTToolType = true; - break; - } - } - } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) { - // MultiTouch Sync: The driver has returned all data for *one* of the pointers. - mCurrentSlot += 1; - } -} - -void MultiTouchMotionAccumulator::finishSync() { - if (!mUsingSlotsProtocol) { - resetSlots(); - } -} - -bool MultiTouchMotionAccumulator::hasStylus() const { - return mHaveStylus; -} - -void MultiTouchMotionAccumulator::warnIfNotInUse(const RawEvent& event, const Slot& slot) { - if (!slot.mInUse) { - ALOGW("Received unexpected event (0x%0x, 0x%0x) for slot %i with tracking id %i", - event.code, event.value, mCurrentSlot, slot.mAbsMTTrackingId); - } -} - -// --- MultiTouchMotionAccumulator::Slot --- - -int32_t MultiTouchMotionAccumulator::Slot::getToolType() const { - if (mHaveAbsMTToolType) { - switch (mAbsMTToolType) { - case MT_TOOL_FINGER: - return AMOTION_EVENT_TOOL_TYPE_FINGER; - case MT_TOOL_PEN: - return AMOTION_EVENT_TOOL_TYPE_STYLUS; - case MT_TOOL_PALM: - return AMOTION_EVENT_TOOL_TYPE_PALM; - } - } - return AMOTION_EVENT_TOOL_TYPE_UNKNOWN; -} - // --- MultiTouchInputMapper --- MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext) diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h index 047e62de62..ddf9e80a6c 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h @@ -17,77 +17,10 @@ #pragma once #include "TouchInputMapper.h" +#include "accumulator/MultiTouchMotionAccumulator.h" namespace android { -/* Keeps track of the state of multi-touch protocol. */ -class MultiTouchMotionAccumulator { -public: - class Slot { - public: - inline bool isInUse() const { return mInUse; } - inline int32_t getX() const { return mAbsMTPositionX; } - inline int32_t getY() const { return mAbsMTPositionY; } - inline int32_t getTouchMajor() const { return mAbsMTTouchMajor; } - inline int32_t getTouchMinor() const { - return mHaveAbsMTTouchMinor ? mAbsMTTouchMinor : mAbsMTTouchMajor; - } - inline int32_t getToolMajor() const { return mAbsMTWidthMajor; } - inline int32_t getToolMinor() const { - return mHaveAbsMTWidthMinor ? mAbsMTWidthMinor : mAbsMTWidthMajor; - } - inline int32_t getOrientation() const { return mAbsMTOrientation; } - inline int32_t getTrackingId() const { return mAbsMTTrackingId; } - inline int32_t getPressure() const { return mAbsMTPressure; } - inline int32_t getDistance() const { return mAbsMTDistance; } - inline int32_t getToolType() const; - - private: - friend class MultiTouchMotionAccumulator; - - bool mInUse = false; - bool mHaveAbsMTTouchMinor = false; - bool mHaveAbsMTWidthMinor = false; - bool mHaveAbsMTToolType = false; - - int32_t mAbsMTPositionX = 0; - int32_t mAbsMTPositionY = 0; - int32_t mAbsMTTouchMajor = 0; - int32_t mAbsMTTouchMinor = 0; - int32_t mAbsMTWidthMajor = 0; - int32_t mAbsMTWidthMinor = 0; - int32_t mAbsMTOrientation = 0; - int32_t mAbsMTTrackingId = -1; - int32_t mAbsMTPressure = 0; - int32_t mAbsMTDistance = 0; - int32_t mAbsMTToolType = 0; - - void clear() { *this = Slot(); } - }; - - MultiTouchMotionAccumulator(); - - void configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol); - void process(const RawEvent* rawEvent); - void finishSync(); - bool hasStylus() const; - - inline size_t getSlotCount() const { return mSlots.size(); } - inline const Slot& getSlot(size_t index) const { - LOG_ALWAYS_FATAL_IF(index < 0 || index >= mSlots.size(), "Invalid index: %zu", index); - return mSlots[index]; - } - -private: - int32_t mCurrentSlot; - std::vector<Slot> mSlots; - bool mUsingSlotsProtocol; - bool mHaveStylus; - - void resetSlots(); - void warnIfNotInUse(const RawEvent& event, const Slot& slot); -}; - class MultiTouchInputMapper : public TouchInputMapper { public: explicit MultiTouchInputMapper(InputDeviceContext& deviceContext); diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 2110107fc1..3947cf7b9f 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -830,6 +830,8 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) mDeviceMode = DeviceMode::POINTER; if (hasStylus()) { mSource |= AINPUT_SOURCE_STYLUS; + } else { + mSource |= AINPUT_SOURCE_TOUCHPAD; } } else if (isTouchScreen()) { mSource = AINPUT_SOURCE_TOUCHSCREEN; diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp new file mode 100644 index 0000000000..b0cef676fb --- /dev/null +++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (C) 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. + */ + +// clang-format off +#include "../Macros.h" +// clang-format on +#include "MultiTouchMotionAccumulator.h" + +namespace android { + +// --- MultiTouchMotionAccumulator --- + +MultiTouchMotionAccumulator::MultiTouchMotionAccumulator() + : mCurrentSlot(-1), mUsingSlotsProtocol(false), mHaveStylus(false) {} + +void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, size_t slotCount, + bool usingSlotsProtocol) { + mUsingSlotsProtocol = usingSlotsProtocol; + mHaveStylus = deviceContext.hasAbsoluteAxis(ABS_MT_TOOL_TYPE); + mSlots = std::vector<Slot>(slotCount); + + mCurrentSlot = -1; + if (mUsingSlotsProtocol) { + // Query the driver for the current slot index and use it as the initial slot before we + // start reading events from the device. It is possible that the current slot index will + // not be the same as it was when the first event was written into the evdev buffer, which + // means the input mapper could start out of sync with the initial state of the events in + // the evdev buffer. In the extremely unlikely case that this happens, the data from two + // slots will be confused until the next ABS_MT_SLOT event is received. This can cause the + // touch point to "jump", but at least there will be no stuck touches. + int32_t initialSlot; + if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot); + status == OK) { + mCurrentSlot = initialSlot; + } else { + ALOGD("Could not retrieve current multi-touch slot index. status=%d", status); + } + } +} + +void MultiTouchMotionAccumulator::resetSlots() { + for (Slot& slot : mSlots) { + slot.clear(); + } + mCurrentSlot = -1; +} + +void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { + if (rawEvent->type == EV_ABS) { + bool newSlot = false; + if (mUsingSlotsProtocol) { + if (rawEvent->code == ABS_MT_SLOT) { + mCurrentSlot = rawEvent->value; + newSlot = true; + } + } else if (mCurrentSlot < 0) { + mCurrentSlot = 0; + } + + if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlots.size()) { + if (newSlot) { + ALOGW_IF(DEBUG_POINTERS, + "MultiTouch device emitted invalid slot index %d but it " + "should be between 0 and %zd; ignoring this slot.", + mCurrentSlot, mSlots.size() - 1); + } + } else { + Slot& slot = mSlots[mCurrentSlot]; + // If mUsingSlotsProtocol is true, it means the raw pointer has axis info of + // ABS_MT_TRACKING_ID and ABS_MT_SLOT, so driver should send a valid trackingId while + // updating the slot. + if (!mUsingSlotsProtocol) { + slot.mInUse = true; + } + + switch (rawEvent->code) { + case ABS_MT_POSITION_X: + slot.mAbsMtPositionX = rawEvent->value; + warnIfNotInUse(*rawEvent, slot); + break; + case ABS_MT_POSITION_Y: + slot.mAbsMtPositionY = rawEvent->value; + warnIfNotInUse(*rawEvent, slot); + break; + case ABS_MT_TOUCH_MAJOR: + slot.mAbsMtTouchMajor = rawEvent->value; + break; + case ABS_MT_TOUCH_MINOR: + slot.mAbsMtTouchMinor = rawEvent->value; + slot.mHaveAbsMtTouchMinor = true; + break; + case ABS_MT_WIDTH_MAJOR: + slot.mAbsMtWidthMajor = rawEvent->value; + break; + case ABS_MT_WIDTH_MINOR: + slot.mAbsMtWidthMinor = rawEvent->value; + slot.mHaveAbsMtWidthMinor = true; + break; + case ABS_MT_ORIENTATION: + slot.mAbsMtOrientation = rawEvent->value; + break; + case ABS_MT_TRACKING_ID: + if (mUsingSlotsProtocol && rawEvent->value < 0) { + // The slot is no longer in use but it retains its previous contents, + // which may be reused for subsequent touches. + slot.mInUse = false; + } else { + slot.mInUse = true; + slot.mAbsMtTrackingId = rawEvent->value; + } + break; + case ABS_MT_PRESSURE: + slot.mAbsMtPressure = rawEvent->value; + break; + case ABS_MT_DISTANCE: + slot.mAbsMtDistance = rawEvent->value; + break; + case ABS_MT_TOOL_TYPE: + slot.mAbsMtToolType = rawEvent->value; + slot.mHaveAbsMtToolType = true; + break; + } + } + } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) { + // MultiTouch Sync: The driver has returned all data for *one* of the pointers. + mCurrentSlot += 1; + } +} + +void MultiTouchMotionAccumulator::finishSync() { + if (!mUsingSlotsProtocol) { + resetSlots(); + } +} + +bool MultiTouchMotionAccumulator::hasStylus() const { + return mHaveStylus; +} + +void MultiTouchMotionAccumulator::warnIfNotInUse(const RawEvent& event, const Slot& slot) { + if (!slot.mInUse) { + ALOGW("Received unexpected event (0x%0x, 0x%0x) for slot %i with tracking id %i", + event.code, event.value, mCurrentSlot, slot.mAbsMtTrackingId); + } +} + +// --- MultiTouchMotionAccumulator::Slot --- + +int32_t MultiTouchMotionAccumulator::Slot::getToolType() const { + if (mHaveAbsMtToolType) { + switch (mAbsMtToolType) { + case MT_TOOL_FINGER: + return AMOTION_EVENT_TOOL_TYPE_FINGER; + case MT_TOOL_PEN: + return AMOTION_EVENT_TOOL_TYPE_STYLUS; + case MT_TOOL_PALM: + return AMOTION_EVENT_TOOL_TYPE_PALM; + } + } + return AMOTION_EVENT_TOOL_TYPE_UNKNOWN; +} + +} // namespace android diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h new file mode 100644 index 0000000000..625a00fdd4 --- /dev/null +++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 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. + */ + +#pragma once + +#include <linux/input-event-codes.h> +#include <stdint.h> +#include <vector> + +#include "EventHub.h" +#include "InputDevice.h" + +namespace android { + +/* Keeps track of the state of multi-touch protocol. */ +class MultiTouchMotionAccumulator { +public: + class Slot { + public: + inline bool isInUse() const { return mInUse; } + inline int32_t getX() const { return mAbsMtPositionX; } + inline int32_t getY() const { return mAbsMtPositionY; } + inline int32_t getTouchMajor() const { return mAbsMtTouchMajor; } + inline int32_t getTouchMinor() const { + return mHaveAbsMtTouchMinor ? mAbsMtTouchMinor : mAbsMtTouchMajor; + } + inline int32_t getToolMajor() const { return mAbsMtWidthMajor; } + inline int32_t getToolMinor() const { + return mHaveAbsMtWidthMinor ? mAbsMtWidthMinor : mAbsMtWidthMajor; + } + inline int32_t getOrientation() const { return mAbsMtOrientation; } + inline int32_t getTrackingId() const { return mAbsMtTrackingId; } + inline int32_t getPressure() const { return mAbsMtPressure; } + inline int32_t getDistance() const { return mAbsMtDistance; } + int32_t getToolType() const; + + private: + friend class MultiTouchMotionAccumulator; + + bool mInUse = false; + bool mHaveAbsMtTouchMinor = false; + bool mHaveAbsMtWidthMinor = false; + bool mHaveAbsMtToolType = false; + + int32_t mAbsMtPositionX = 0; + int32_t mAbsMtPositionY = 0; + int32_t mAbsMtTouchMajor = 0; + int32_t mAbsMtTouchMinor = 0; + int32_t mAbsMtWidthMajor = 0; + int32_t mAbsMtWidthMinor = 0; + int32_t mAbsMtOrientation = 0; + int32_t mAbsMtTrackingId = -1; + int32_t mAbsMtPressure = 0; + int32_t mAbsMtDistance = 0; + int32_t mAbsMtToolType = 0; + + void clear() { *this = Slot(); } + }; + + MultiTouchMotionAccumulator(); + + void configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol); + void process(const RawEvent* rawEvent); + void finishSync(); + bool hasStylus() const; + + inline size_t getSlotCount() const { return mSlots.size(); } + inline const Slot& getSlot(size_t index) const { + LOG_ALWAYS_FATAL_IF(index < 0 || index >= mSlots.size(), "Invalid index: %zu", index); + return mSlots[index]; + } + +private: + int32_t mCurrentSlot; + std::vector<Slot> mSlots; + bool mUsingSlotsProtocol; + bool mHaveStylus; + + void resetSlots(); + void warnIfNotInUse(const RawEvent& event, const Slot& slot); +}; + +} // namespace android diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 0a5793d49a..e4e22df5de 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -2373,7 +2373,7 @@ TEST_F(InputReaderIntegrationTest, TestInvalidDevice) { // An invalid input device that is only used for this test. class InvalidUinputDevice : public UinputDevice { public: - InvalidUinputDevice() : UinputDevice("Invalid Device") {} + InvalidUinputDevice() : UinputDevice("Invalid Device", 99 /*productId*/) {} private: void configureDevice(int fd, uinput_user_dev* device) override {} @@ -2825,6 +2825,96 @@ TEST_F(TouchIntegrationTest, StylusButtonsGenerateKeyEvents) { WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY)))); } +TEST_F(TouchIntegrationTest, StylusButtonsSurroundingTouchGesture) { + const Point centerPoint = mDevice->getCenterPoint(); + + // Press the stylus button. + mDevice->sendKey(BTN_STYLUS, 1); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD), + WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY)))); + + // Start and finish a stylus gesture. + mDevice->sendSlot(FIRST_SLOT); + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendToolType(MT_TOOL_PEN); + mDevice->sendDown(centerPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + + mDevice->sendTrackingId(INVALID_TRACKING_ID); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0)))); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0)))); + + // Release the stylus button. + mDevice->sendKey(BTN_STYLUS, 0); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD), + WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY)))); +} + +TEST_F(TouchIntegrationTest, StylusButtonsWithinTouchGesture) { + const Point centerPoint = mDevice->getCenterPoint(); + + // Start a stylus gesture. + mDevice->sendSlot(FIRST_SLOT); + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendToolType(MT_TOOL_PEN); + mDevice->sendDown(centerPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0)))); + + // Press and release a stylus button. Each change in button state also generates a MOVE event. + mDevice->sendKey(BTN_STYLUS, 1); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD), + WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), + WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)))); + + mDevice->sendKey(BTN_STYLUS, 0); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled( + AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD), + WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY)))); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0)))); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0)))); + + // Finish the stylus gesture. + mDevice->sendTrackingId(INVALID_TRACKING_ID); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0)))); +} + // --- InputDeviceTest --- class InputDeviceTest : public testing::Test { protected: @@ -5666,7 +5756,7 @@ TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndNot prepareAxes(POSITION); SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>(); - ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); + ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); } TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchScreen_ReturnsTouchScreen) { @@ -9038,8 +9128,8 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShouldHandleDisplayId) { prepareAxes(POSITION); MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>(); - // Check source is mouse that would obtain the PointerController. - ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); + // Check source is a touchpad that would obtain the PointerController. + ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); NotifyMotionArgs motionArgs; processPosition(mapper, 100, 100); @@ -10066,11 +10156,11 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); - // non captured touchpad should be a mouse source + // A non captured touchpad should have a mouse and touchpad source. mFakePolicy->setPointerCapture(false); configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); - ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); + ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); } TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) { @@ -10129,10 +10219,10 @@ TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_GetSources) { mFakePolicy->setPointerCapture(false); MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>(); - // uncaptured touchpad should be a pointer device - ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); + // An uncaptured touchpad should be a pointer device, with additional touchpad source. + ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); - // captured touchpad should be a touchpad device + // A captured touchpad should just have a touchpad source. mFakePolicy->setPointerCapture(true); configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE); ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); diff --git a/services/inputflinger/tests/UinputDevice.cpp b/services/inputflinger/tests/UinputDevice.cpp index c4830dc815..bc695b8bd8 100644 --- a/services/inputflinger/tests/UinputDevice.cpp +++ b/services/inputflinger/tests/UinputDevice.cpp @@ -24,7 +24,8 @@ namespace android { // --- UinputDevice --- -UinputDevice::UinputDevice(const char* name) : mName(name) {} +UinputDevice::UinputDevice(const char* name, int16_t productId) + : mName(name), mProductId(productId) {} UinputDevice::~UinputDevice() { if (ioctl(mDeviceFd, UI_DEV_DESTROY)) { @@ -43,7 +44,7 @@ void UinputDevice::init() { strlcpy(device.name, mName, UINPUT_MAX_NAME_SIZE); device.id.bustype = BUS_USB; device.id.vendor = 0x01; - device.id.product = 0x01; + device.id.product = mProductId; device.id.version = 1; ASSERT_NO_FATAL_FAILURE(configureDevice(mDeviceFd, &device)); @@ -76,8 +77,8 @@ void UinputDevice::injectEvent(uint16_t type, uint16_t code, int32_t value) { // --- UinputKeyboard --- -UinputKeyboard::UinputKeyboard(const char* name, std::initializer_list<int> keys) - : UinputDevice(name), mKeys(keys.begin(), keys.end()) {} +UinputKeyboard::UinputKeyboard(const char* name, int16_t productId, std::initializer_list<int> keys) + : UinputDevice(name, productId), mKeys(keys.begin(), keys.end()) {} void UinputKeyboard::configureDevice(int fd, uinput_user_dev* device) { // enable key press/release event @@ -121,23 +122,26 @@ void UinputKeyboard::pressAndReleaseKey(int key) { // --- UinputHomeKey --- -UinputHomeKey::UinputHomeKey() : UinputKeyboard("Test Uinput Home Key", {KEY_HOME}) {} +UinputHomeKey::UinputHomeKey() : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, {KEY_HOME}) {} void UinputHomeKey::pressAndReleaseHomeKey() { pressAndReleaseKey(KEY_HOME); } -// --- UinputSteamController +// --- UinputSteamController --- + UinputSteamController::UinputSteamController() - : UinputKeyboard("Test Uinput Steam Controller", {BTN_GEAR_DOWN, BTN_GEAR_UP}) {} + : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, {BTN_GEAR_DOWN, BTN_GEAR_UP}) {} + +// --- UinputExternalStylus --- -// --- UinputExternalStylus UinputExternalStylus::UinputExternalStylus() - : UinputKeyboard("Test Uinput External Stylus", {BTN_STYLUS, BTN_STYLUS2, BTN_STYLUS3}) {} + : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, {BTN_STYLUS, BTN_STYLUS2, BTN_STYLUS3}) {} // --- UinputTouchScreen --- -UinputTouchScreen::UinputTouchScreen(const Rect* size) - : UinputDevice(UinputTouchScreen::DEVICE_NAME), mSize(*size) {} + +UinputTouchScreen::UinputTouchScreen(const Rect& size) + : UinputDevice(DEVICE_NAME, PRODUCT_ID), mSize(size) {} void UinputTouchScreen::configureDevice(int fd, uinput_user_dev* device) { // Setup the touch screen device diff --git a/services/inputflinger/tests/UinputDevice.h b/services/inputflinger/tests/UinputDevice.h index 53dcfd0a68..d661bd36d3 100644 --- a/services/inputflinger/tests/UinputDevice.h +++ b/services/inputflinger/tests/UinputDevice.h @@ -32,7 +32,7 @@ namespace android { template <class D, class... Ts> std::unique_ptr<D> createUinputDevice(Ts... args) { // Using `new` to access non-public constructors. - std::unique_ptr<D> dev(new D(&args...)); + std::unique_ptr<D> dev(new D(args...)); EXPECT_NO_FATAL_FAILURE(dev->init()); return dev; } @@ -51,8 +51,9 @@ public: protected: const char* mName; + const int16_t mProductId; - UinputDevice(const char* name); + explicit UinputDevice(const char* name, int16_t productId); // Signals which types of events this device supports before it is created. // This must be overridden by subclasses. @@ -71,7 +72,8 @@ private: class UinputKeyboard : public UinputDevice { public: - static constexpr const char* KEYBOARD_NAME = "Test Keyboard Device"; + static constexpr const char* KEYBOARD_NAME = "Test Uinput Keyboard Device"; + static constexpr int16_t PRODUCT_ID = 42; // Injects key press and sync. void pressKey(int key); @@ -84,7 +86,8 @@ public: friend std::unique_ptr<D> createUinputDevice(Ts... args); protected: - UinputKeyboard(const char* name, std::initializer_list<int> keys = {}); + explicit UinputKeyboard(const char* name, int16_t productId = PRODUCT_ID, + std::initializer_list<int> keys = {}); private: void configureDevice(int fd, uinput_user_dev* device) override; @@ -97,6 +100,9 @@ private: // A keyboard device that has a single HOME key. class UinputHomeKey : public UinputKeyboard { public: + static constexpr const char* DEVICE_NAME = "Test Uinput Home Key"; + static constexpr int16_t PRODUCT_ID = 43; + // Injects 4 events: key press, sync, key release, and sync. void pressAndReleaseHomeKey(); @@ -104,34 +110,47 @@ public: friend std::unique_ptr<D> createUinputDevice(Ts... args); private: - UinputHomeKey(); + explicit UinputHomeKey(); }; +// --- UinputSteamController --- + // A joystick device that sends a BTN_GEAR_DOWN / BTN_WHEEL key. class UinputSteamController : public UinputKeyboard { public: + static constexpr const char* DEVICE_NAME = "Test Uinput Steam Controller"; + static constexpr int16_t PRODUCT_ID = 44; + template <class D, class... Ts> friend std::unique_ptr<D> createUinputDevice(Ts... args); private: - UinputSteamController(); + explicit UinputSteamController(); }; +// --- UinputExternalStylus --- + // A stylus that reports button presses. class UinputExternalStylus : public UinputKeyboard { public: + static constexpr const char* DEVICE_NAME = "Test Uinput External Stylus"; + static constexpr int16_t PRODUCT_ID = 45; + template <class D, class... Ts> friend std::unique_ptr<D> createUinputDevice(Ts... args); private: - UinputExternalStylus(); + explicit UinputExternalStylus(); }; // --- UinputTouchScreen --- -// A touch screen device with specific size. + +// A multi-touch touchscreen device with specific size that also supports styluses. class UinputTouchScreen : public UinputDevice { public: - static constexpr const char* DEVICE_NAME = "Test Touch Screen"; + static constexpr const char* DEVICE_NAME = "Test Uinput Touch Screen"; + static constexpr int16_t PRODUCT_ID = 46; + static const int32_t RAW_TOUCH_MIN = 0; static const int32_t RAW_TOUCH_MAX = 31; static const int32_t RAW_ID_MIN = 0; @@ -157,7 +176,7 @@ public: const Point getCenterPoint(); protected: - UinputTouchScreen(const Rect* size); + explicit UinputTouchScreen(const Rect& size); private: void configureDevice(int fd, uinput_user_dev* device) override; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h index 8661063e12..7c10fa57ec 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h @@ -58,7 +58,7 @@ public: virtual renderengine::RenderEngine& getRenderEngine() const = 0; virtual void setRenderEngine(renderengine::RenderEngine*) = 0; - virtual TimeStats& getTimeStats() const = 0; + virtual TimeStats* getTimeStats() const = 0; virtual void setTimeStats(const std::shared_ptr<TimeStats>&) = 0; virtual bool needsAnotherUpdate() const = 0; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h index 2af6c80f1e..c6995576a1 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h @@ -36,7 +36,7 @@ public: renderengine::RenderEngine& getRenderEngine() const override; void setRenderEngine(renderengine::RenderEngine*) override; - TimeStats& getTimeStats() const override; + TimeStats* getTimeStats() const override; void setTimeStats(const std::shared_ptr<TimeStats>&) override; bool needsAnotherUpdate() const override; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h index fc71649949..9b2387b966 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h @@ -42,7 +42,7 @@ public: MOCK_CONST_METHOD0(getRenderEngine, renderengine::RenderEngine&()); MOCK_METHOD1(setRenderEngine, void(renderengine::RenderEngine*)); - MOCK_CONST_METHOD0(getTimeStats, TimeStats&()); + MOCK_CONST_METHOD0(getTimeStats, TimeStats*()); MOCK_METHOD1(setTimeStats, void(const std::shared_ptr<TimeStats>&)); MOCK_CONST_METHOD0(needsAnotherUpdate, bool()); diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp index e42f11a9b9..15fadbc8ee 100644 --- a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp +++ b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp @@ -72,8 +72,8 @@ void CompositionEngine::setRenderEngine(renderengine::RenderEngine* renderEngine mRenderEngine = renderEngine; } -TimeStats& CompositionEngine::getTimeStats() const { - return *mTimeStats.get(); +TimeStats* CompositionEngine::getTimeStats() const { + return mTimeStats.get(); } void CompositionEngine::setTimeStats(const std::shared_ptr<TimeStats>& timeStats) { diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 0622534491..c2b1f0679c 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -1178,15 +1178,9 @@ void Output::updateProtectedContentState() { bool needsProtected = std::any_of(layers.begin(), layers.end(), [](auto* layer) { return layer->getLayerFE().getCompositionState()->hasProtectedContent; }); - if (needsProtected != renderEngine.isProtected()) { - renderEngine.useProtectedContext(needsProtected); - } - if (needsProtected != mRenderSurface->isProtected() && - needsProtected == renderEngine.isProtected()) { + if (needsProtected != mRenderSurface->isProtected()) { mRenderSurface->setProtected(needsProtected); } - } else if (!outputState.isSecure && renderEngine.isProtected()) { - renderEngine.useProtectedContext(false); } } @@ -1340,10 +1334,13 @@ std::optional<base::unique_fd> Output::composeSurfaces( const auto fence = std::move(fenceResult).value_or(Fence::NO_FENCE); - if (auto& timeStats = getCompositionEngine().getTimeStats(); fence->isValid()) { - timeStats.recordRenderEngineDuration(renderEngineStart, std::make_shared<FenceTime>(fence)); - } else { - timeStats.recordRenderEngineDuration(renderEngineStart, systemTime()); + if (auto timeStats = getCompositionEngine().getTimeStats()) { + if (fence->isValid()) { + timeStats->recordRenderEngineDuration(renderEngineStart, + std::make_shared<FenceTime>(fence)); + } else { + timeStats->recordRenderEngineDuration(renderEngineStart, systemTime()); + } } for (auto* clientComposedLayer : clientCompositionLayersFE) { diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp index 8d99f894a6..60ed660c7a 100644 --- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp @@ -71,7 +71,7 @@ TEST_F(CompositionEngineTest, canSetRenderEngine) { TEST_F(CompositionEngineTest, canSetTimeStats) { mEngine.setTimeStats(mTimeStats); - EXPECT_EQ(mTimeStats.get(), &mEngine.getTimeStats()); + EXPECT_EQ(mTimeStats.get(), mEngine.getTimeStats()); } /* diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp index 514a8ff8fc..21099876f4 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp @@ -3332,8 +3332,7 @@ struct OutputComposeSurfacesTest : public testing::Test { EXPECT_CALL(mOutput, getCompositionEngine()).WillRepeatedly(ReturnRef(mCompositionEngine)); EXPECT_CALL(mCompositionEngine, getRenderEngine()).WillRepeatedly(ReturnRef(mRenderEngine)); - EXPECT_CALL(mCompositionEngine, getTimeStats()) - .WillRepeatedly(ReturnRef(*mTimeStats.get())); + EXPECT_CALL(mCompositionEngine, getTimeStats()).WillRepeatedly(Return(mTimeStats.get())); EXPECT_CALL(*mDisplayColorProfile, getHdrCapabilities()) .WillRepeatedly(ReturnRef(kHdrCapabilities)); } @@ -4010,39 +4009,11 @@ struct OutputComposeSurfacesTest_HandlesProtectedContent : public OutputComposeS Layer mLayer2; }; -TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifDisplayIsNotSecure) { - mOutput.mState.isSecure = false; - mLayer2.mLayerFEState.hasProtectedContent = true; - EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true)); - EXPECT_CALL(mRenderEngine, useProtectedContext(false)); - - base::unique_fd fd; - std::shared_ptr<renderengine::ExternalTexture> tex; - mOutput.updateProtectedContentState(); - mOutput.dequeueRenderBuffer(&fd, &tex); - mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd); -} - -TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifRenderEngineDoesNotSupportIt) { - mOutput.mState.isSecure = true; - mLayer2.mLayerFEState.hasProtectedContent = true; - EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false)); - - base::unique_fd fd; - std::shared_ptr<renderengine::ExternalTexture> tex; - mOutput.updateProtectedContentState(); - mOutput.dequeueRenderBuffer(&fd, &tex); - mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd); -} - TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNoProtectedContentLayers) { mOutput.mState.isSecure = true; mLayer2.mLayerFEState.hasProtectedContent = false; EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true)).WillOnce(Return(false)); EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true)); - EXPECT_CALL(mRenderEngine, useProtectedContext(false)); EXPECT_CALL(*mRenderSurface, setProtected(false)); base::unique_fd fd; @@ -4060,10 +4031,7 @@ TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNotEnabled) { // For this test, we also check the call order of key functions. InSequence seq; - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(false)); - EXPECT_CALL(mRenderEngine, useProtectedContext(true)); EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false)); - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true)); EXPECT_CALL(*mRenderSurface, setProtected(true)); // Must happen after setting the protected content state. EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer)); @@ -4081,7 +4049,6 @@ TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledEveryw mOutput.mState.isSecure = true; mLayer2.mLayerFEState.hasProtectedContent = true; EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true)); EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true)); base::unique_fd fd; @@ -4091,43 +4058,11 @@ TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledEveryw mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd); } -TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifFailsToEnableInRenderEngine) { - mOutput.mState.isSecure = true; - mLayer2.mLayerFEState.hasProtectedContent = true; - EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(false)).WillOnce(Return(false)); - EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false)); - EXPECT_CALL(mRenderEngine, useProtectedContext(true)); - - base::unique_fd fd; - std::shared_ptr<renderengine::ExternalTexture> tex; - mOutput.updateProtectedContentState(); - mOutput.dequeueRenderBuffer(&fd, &tex); - mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd); -} - -TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledInRenderEngine) { - mOutput.mState.isSecure = true; - mLayer2.mLayerFEState.hasProtectedContent = true; - EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true)).WillOnce(Return(true)); - EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false)); - EXPECT_CALL(*mRenderSurface, setProtected(true)); - - base::unique_fd fd; - std::shared_ptr<renderengine::ExternalTexture> tex; - mOutput.updateProtectedContentState(); - mOutput.dequeueRenderBuffer(&fd, &tex); - mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd); -} - TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledInRenderSurface) { mOutput.mState.isSecure = true; mLayer2.mLayerFEState.hasProtectedContent = true; EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true)); - EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(false)); EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true)); - EXPECT_CALL(mRenderEngine, useProtectedContext(true)); base::unique_fd fd; std::shared_ptr<renderengine::ExternalTexture> tex; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 3478eaaa92..d1e61a3273 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -472,7 +472,7 @@ void Layer::prepareBasicGeometryCompositionState() { snapshot->geomLayerTransform = getTransform(); snapshot->geomInverseLayerTransform = snapshot->geomLayerTransform.inverse(); snapshot->transparentRegionHint = getActiveTransparentRegion(drawingState); - + snapshot->blurRegionTransform = getActiveTransform(drawingState).inverse(); snapshot->blendMode = static_cast<Hwc2::IComposerClient::BlendMode>(blendMode); snapshot->alpha = alpha; snapshot->backgroundBlurRadius = drawingState.backgroundBlurRadius; diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp index 3bdb521d0e..363adc641e 100644 --- a/services/surfaceflinger/LayerFE.cpp +++ b/services/surfaceflinger/LayerFE.cpp @@ -148,14 +148,14 @@ std::optional<compositionengine::LayerFE::LayerSettings> LayerFE::prepareClientC case LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled: layerSettings.backgroundBlurRadius = mSnapshot->backgroundBlurRadius; layerSettings.blurRegions = mSnapshot->blurRegions; - layerSettings.blurRegionTransform = mSnapshot->geomInverseLayerTransform.asMatrix4(); + layerSettings.blurRegionTransform = mSnapshot->blurRegionTransform.asMatrix4(); break; case LayerFE::ClientCompositionTargetSettings::BlurSetting::BackgroundBlurOnly: layerSettings.backgroundBlurRadius = mSnapshot->backgroundBlurRadius; break; case LayerFE::ClientCompositionTargetSettings::BlurSetting::BlurRegionsOnly: layerSettings.blurRegions = mSnapshot->blurRegions; - layerSettings.blurRegionTransform = mSnapshot->geomInverseLayerTransform.asMatrix4(); + layerSettings.blurRegionTransform = mSnapshot->blurRegionTransform.asMatrix4(); break; case LayerFE::ClientCompositionTargetSettings::BlurSetting::Disabled: default: diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h index e4f6889762..822bcb7a94 100644 --- a/services/surfaceflinger/LayerFE.h +++ b/services/surfaceflinger/LayerFE.h @@ -57,6 +57,7 @@ struct LayerSnapshot : public compositionengine::LayerFECompositionState { gui::LayerMetadata relativeLayerMetadata; bool contentDirty; bool hasReadyFrame; + ui::Transform blurRegionTransform; }; struct CompositionResult { diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index 40af6ee575..c913891b62 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -30,6 +30,7 @@ #include <ftl/enum.h> #include <ftl/fake_guard.h> #include <ftl/match.h> +#include <ftl/unit.h> #include <utils/Trace.h> #include "../SurfaceFlingerProperties.h" @@ -105,7 +106,7 @@ std::vector<DisplayModeIterator> sortByRefreshRate(const DisplayModes& modes, Fi return sortedModes; } -bool canModesSupportFrameRateOverride(const std::vector<DisplayModeIterator>& sortedModes) { +bool shouldEnableFrameRateOverride(const std::vector<DisplayModeIterator>& sortedModes) { for (const auto it1 : sortedModes) { const auto& mode1 = it1->second; for (const auto it2 : sortedModes) { @@ -164,9 +165,10 @@ struct RefreshRateSelector::RefreshRateScoreComparator { std::string RefreshRateSelector::Policy::toString() const { return base::StringPrintf("{defaultModeId=%d, allowGroupSwitching=%s" - ", primaryRange=%s, appRequestRange=%s}", + ", primaryRanges=%s, appRequestRanges=%s}", defaultMode.value(), allowGroupSwitching ? "true" : "false", - to_string(primaryRange).c_str(), to_string(appRequestRange).c_str()); + to_string(primaryRanges).c_str(), + to_string(appRequestRanges).c_str()); } std::pair<nsecs_t, nsecs_t> RefreshRateSelector::getDisplayFrames(nsecs_t layerPeriod, @@ -263,7 +265,7 @@ float RefreshRateSelector::calculateLayerScoreLocked(const LayerRequirement& lay if (layer.vote == LayerVoteType::ExplicitExact) { const int divisor = getFrameRateDivisor(refreshRate, layer.desiredRefreshRate); - if (mSupportsFrameRateOverrideByContent) { + if (supportsFrameRateOverrideByContent()) { // Since we support frame rate override, allow refresh rates which are // multiples of the layer's request, as those apps would be throttled // down to run at the desired refresh rate. @@ -381,7 +383,7 @@ auto RefreshRateSelector::getRankedRefreshRatesLocked(const std::vector<LayerReq // move out the of range if layers explicitly request a different refresh // rate. const bool primaryRangeIsSingleRate = - isApproxEqual(policy->primaryRange.min, policy->primaryRange.max); + isApproxEqual(policy->primaryRanges.physical.min, policy->primaryRanges.physical.max); if (!signals.touch && signals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) { ALOGV("Idle"); @@ -450,7 +452,7 @@ auto RefreshRateSelector::getRankedRefreshRatesLocked(const std::vector<LayerReq continue; } - const bool inPrimaryRange = policy->primaryRange.includes(mode->getFps()); + const bool inPrimaryRange = policy->primaryRanges.physical.includes(mode->getFps()); if ((primaryRangeIsSingleRate || !inPrimaryRange) && !(layer.focused && (layer.vote == LayerVoteType::ExplicitDefault || @@ -578,7 +580,7 @@ auto RefreshRateSelector::getRankedRefreshRatesLocked(const std::vector<LayerReq // vote we should not change it if we get a touch event. Only apply touch boost if it will // actually increase the refresh rate over the normal selection. const bool touchBoostForExplicitExact = [&] { - if (mSupportsFrameRateOverrideByContent) { + if (supportsFrameRateOverrideByContent()) { // Enable touch boost if there are other layers besides exact return explicitExact + noVoteLayers != layers.size(); } else { @@ -647,23 +649,43 @@ auto RefreshRateSelector::getFrameRateOverrides(const std::vector<LayerRequireme GlobalSignals globalSignals) const -> UidToFrameRateOverride { ATRACE_CALL(); - ALOGV("%s: %zu layers", __func__, layers.size()); std::lock_guard lock(mLock); - std::vector<RefreshRateScore> scores; - scores.reserve(mDisplayModes.size()); - - for (auto it = mDisplayModes.begin(); it != mDisplayModes.end(); ++it) { - scores.emplace_back(RefreshRateScore{it, 0.0f}); + // Prepare a set of supported display refresh rates for easy lookup + constexpr size_t kStaticCapacity = 8; + ftl::SmallMap<Fps, ftl::Unit, kStaticCapacity, FpsApproxEqual> supportedDisplayRefreshRates; + if (mConfig.enableFrameRateOverride == + Config::FrameRateOverride::EnabledForNativeRefreshRates) { + for (const auto& [_, modePtr] : mDisplayModes) { + supportedDisplayRefreshRates.try_emplace(modePtr->getFps(), ftl::unit); + } } - std::sort(scores.begin(), scores.end(), [](const auto& lhs, const auto& rhs) { - const auto& mode1 = lhs.modeIt->second; - const auto& mode2 = rhs.modeIt->second; - return isStrictlyLess(mode1->getFps(), mode2->getFps()); - }); + const auto* policyPtr = getCurrentPolicyLocked(); + // We don't want to run lower than 30fps + const Fps minFrameRate = std::max(policyPtr->appRequestRanges.render.min, 30_Hz, isApproxLess); + + using fps_approx_ops::operator/; + const unsigned numMultiples = displayRefreshRate / minFrameRate; + + std::vector<std::pair<Fps, float>> scoredFrameRates; + scoredFrameRates.reserve(numMultiples); + + for (unsigned n = numMultiples; n > 0; n--) { + const Fps divisor = displayRefreshRate / n; + if (mConfig.enableFrameRateOverride == + Config::FrameRateOverride::EnabledForNativeRefreshRates && + !supportedDisplayRefreshRates.contains(divisor)) { + continue; + } + + if (policyPtr->appRequestRanges.render.includes(divisor)) { + ALOGV("%s: adding %s as a potential frame rate", __func__, to_string(divisor).c_str()); + scoredFrameRates.emplace_back(divisor, 0); + } + } const auto layersByUid = groupLayersByUid(layers); UidToFrameRateOverride frameRateOverrides; @@ -679,7 +701,7 @@ auto RefreshRateSelector::getFrameRateOverrides(const std::vector<LayerRequireme continue; } - for (auto& [_, score, _1] : scores) { + for (auto& [_, score] : scoredFrameRates) { score = 0; } @@ -691,36 +713,33 @@ auto RefreshRateSelector::getFrameRateOverrides(const std::vector<LayerRequireme LOG_ALWAYS_FATAL_IF(layer->vote != LayerVoteType::ExplicitDefault && layer->vote != LayerVoteType::ExplicitExactOrMultiple && layer->vote != LayerVoteType::ExplicitExact); - for (auto& [modeIt, score, _] : scores) { + for (auto& [fps, score] : scoredFrameRates) { constexpr bool isSeamlessSwitch = true; - const auto layerScore = calculateLayerScoreLocked(*layer, modeIt->second->getFps(), - isSeamlessSwitch); + const auto layerScore = calculateLayerScoreLocked(*layer, fps, isSeamlessSwitch); score += layer->weight * layerScore; } } - // We just care about the refresh rates which are a divisor of the - // display refresh rate - const auto it = std::remove_if(scores.begin(), scores.end(), [&](RefreshRateScore score) { - const auto& [id, mode] = *score.modeIt; - return getFrameRateDivisor(displayRefreshRate, mode->getFps()) == 0; - }); - scores.erase(it, scores.end()); - // If we never scored any layers, we don't have a preferred frame rate - if (std::all_of(scores.begin(), scores.end(), - [](RefreshRateScore score) { return score.overallScore == 0; })) { + if (std::all_of(scoredFrameRates.begin(), scoredFrameRates.end(), + [](const auto& scoredFrameRate) { + const auto [_, score] = scoredFrameRate; + return score == 0; + })) { continue; } // Now that we scored all the refresh rates we need to pick the lowest refresh rate // that got the highest score. - const DisplayModePtr& bestRefreshRate = - std::min_element(scores.begin(), scores.end(), - RefreshRateScoreComparator{.refreshRateOrder = - RefreshRateOrder::Ascending}) - ->modeIt->second; - frameRateOverrides.emplace(uid, bestRefreshRate->getFps()); + const auto [overrideFps, _] = + *std::max_element(scoredFrameRates.begin(), scoredFrameRates.end(), + [](const auto& lhsPair, const auto& rhsPair) { + const float lhs = lhsPair.second; + const float rhs = rhsPair.second; + return lhs < rhs && !ScoredRefreshRate::scoresEqual(lhs, rhs); + }); + ALOGV("%s: overriding to %s for uid=%d", __func__, to_string(overrideFps).c_str(), uid); + frameRateOverrides.emplace(uid, overrideFps); } return frameRateOverrides; @@ -893,8 +912,17 @@ void RefreshRateSelector::updateDisplayModes(DisplayModes modes, DisplayModeId a mDisplayManagerPolicy = {}; mDisplayManagerPolicy.defaultMode = activeModeId; - mSupportsFrameRateOverrideByContent = - mConfig.enableFrameRateOverride && canModesSupportFrameRateOverride(sortedModes); + mFrameRateOverrideConfig = [&] { + switch (mConfig.enableFrameRateOverride) { + case Config::FrameRateOverride::Disabled: + case Config::FrameRateOverride::Enabled: + return mConfig.enableFrameRateOverride; + case Config::FrameRateOverride::EnabledForNativeRefreshRates: + return shouldEnableFrameRateOverride(sortedModes) + ? Config::FrameRateOverride::EnabledForNativeRefreshRates + : Config::FrameRateOverride::Disabled; + } + }(); constructAvailableRefreshRates(); } @@ -902,7 +930,7 @@ void RefreshRateSelector::updateDisplayModes(DisplayModes modes, DisplayModeId a bool RefreshRateSelector::isPolicyValidLocked(const Policy& policy) const { // defaultMode must be a valid mode, and within the given refresh rate range. if (const auto mode = mDisplayModes.get(policy.defaultMode)) { - if (!policy.primaryRange.includes(mode->get()->getFps())) { + if (!policy.primaryRanges.physical.includes(mode->get()->getFps())) { ALOGE("Default mode is not in the primary range."); return false; } @@ -912,8 +940,8 @@ bool RefreshRateSelector::isPolicyValidLocked(const Policy& policy) const { } using namespace fps_approx_ops; - return policy.appRequestRange.min <= policy.primaryRange.min && - policy.appRequestRange.max >= policy.primaryRange.max; + return policy.appRequestRanges.physical.min <= policy.primaryRanges.physical.min && + policy.appRequestRanges.physical.max >= policy.primaryRanges.physical.max; } auto RefreshRateSelector::setPolicy(const PolicyVariant& policy) -> SetPolicyResult { @@ -1026,8 +1054,8 @@ void RefreshRateSelector::constructAvailableRefreshRates() { return modes; }; - mPrimaryRefreshRates = filterRefreshRates(policy->primaryRange, "primary"); - mAppRequestRefreshRates = filterRefreshRates(policy->appRequestRange, "app request"); + mPrimaryRefreshRates = filterRefreshRates(policy->primaryRanges.physical, "primary"); + mAppRequestRefreshRates = filterRefreshRates(policy->appRequestRanges.physical, "app request"); } Fps RefreshRateSelector::findClosestKnownFrameRate(Fps frameRate) const { @@ -1067,7 +1095,7 @@ auto RefreshRateSelector::getIdleTimerAction() const -> KernelIdleTimerAction { if (minByPolicy == maxByPolicy) { // Turn on the timer when the min of the primary range is below the device min. if (const Policy* currentPolicy = getCurrentPolicyLocked(); - isApproxLess(currentPolicy->primaryRange.min, deviceMinFps)) { + isApproxLess(currentPolicy->primaryRanges.physical.min, deviceMinFps)) { return KernelIdleTimerAction::TurnOn; } return KernelIdleTimerAction::TurnOff; @@ -1127,7 +1155,7 @@ void RefreshRateSelector::dump(utils::Dumper& dumper) const { dumper.dump("overridePolicy"sv, currentPolicy.toString()); } - dumper.dump("supportsFrameRateOverrideByContent"sv, mSupportsFrameRateOverrideByContent); + dumper.dump("frameRateOverrideConfig"sv, *ftl::enum_name(mFrameRateOverrideConfig)); std::string idleTimer; if (mIdleTimer) { diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h index bff16d3010..abbd30465a 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h @@ -67,40 +67,31 @@ public: DisplayModeId defaultMode; // Whether or not we switch mode groups to get the best frame rate. bool allowGroupSwitching = kAllowGroupSwitchingDefault; - // The primary refresh rate range represents display manager's general guidance on the - // display modes we'll consider when switching refresh rates. Unless we get an explicit - // signal from an app, we should stay within this range. - FpsRange primaryRange; - // The app request refresh rate range allows us to consider more display modes when - // switching refresh rates. Although we should generally stay within the primary range, - // specific considerations, such as layer frame rate settings specified via the - // setFrameRate() api, may cause us to go outside the primary range. We never go outside the - // app request range. The app request range will be greater than or equal to the primary - // refresh rate range, never smaller. - FpsRange appRequestRange; + // The primary refresh rate ranges. @see DisplayModeSpecs.aidl for details. + // TODO(b/257072060): use the render range when selecting SF render rate + // or the app override frame rate + FpsRanges primaryRanges; + // The app request refresh rate ranges. @see DisplayModeSpecs.aidl for details. + FpsRanges appRequestRanges; Policy() = default; - Policy(DisplayModeId defaultMode, FpsRange range) - : Policy(defaultMode, kAllowGroupSwitchingDefault, range, range) {} + Policy(DisplayModeId defaultMode, FpsRange range, + bool allowGroupSwitching = kAllowGroupSwitchingDefault) + : Policy(defaultMode, FpsRanges{range, range}, FpsRanges{range, range}, + allowGroupSwitching) {} - Policy(DisplayModeId defaultMode, bool allowGroupSwitching, FpsRange range) - : Policy(defaultMode, allowGroupSwitching, range, range) {} - - Policy(DisplayModeId defaultMode, FpsRange primaryRange, FpsRange appRequestRange) - : Policy(defaultMode, kAllowGroupSwitchingDefault, primaryRange, appRequestRange) {} - - Policy(DisplayModeId defaultMode, bool allowGroupSwitching, FpsRange primaryRange, - FpsRange appRequestRange) + Policy(DisplayModeId defaultMode, FpsRanges primaryRanges, FpsRanges appRequestRanges, + bool allowGroupSwitching = kAllowGroupSwitchingDefault) : defaultMode(defaultMode), allowGroupSwitching(allowGroupSwitching), - primaryRange(primaryRange), - appRequestRange(appRequestRange) {} + primaryRanges(primaryRanges), + appRequestRanges(appRequestRanges) {} bool operator==(const Policy& other) const { using namespace fps_approx_ops; - return defaultMode == other.defaultMode && primaryRange == other.primaryRange && - appRequestRange == other.appRequestRange && + return defaultMode == other.defaultMode && primaryRanges == other.primaryRanges && + appRequestRanges == other.appRequestRanges && allowGroupSwitching == other.allowGroupSwitching; } @@ -260,7 +251,20 @@ public: // Configuration flags. struct Config { - bool enableFrameRateOverride = false; + enum class FrameRateOverride { + // Do not override the frame rate for an app + Disabled, + + // Override the frame rate for an app to a value which is also + // a display refresh rate + EnabledForNativeRefreshRates, + + // Override the frame rate for an app to any value + Enabled, + + ftl_last = Enabled + }; + FrameRateOverride enableFrameRateOverride = FrameRateOverride::Disabled; // Specifies the upper refresh rate threshold (inclusive) for layer vote types of multiple // or heuristic, such that refresh rates higher than this value will not be voted for. 0 if @@ -275,11 +279,12 @@ public: std::optional<KernelIdleTimerController> kernelIdleTimerController; }; - RefreshRateSelector(DisplayModes, DisplayModeId activeModeId, - Config config = {.enableFrameRateOverride = false, - .frameRateMultipleThreshold = 0, - .idleTimerTimeout = 0ms, - .kernelIdleTimerController = {}}); + RefreshRateSelector( + DisplayModes, DisplayModeId activeModeId, + Config config = {.enableFrameRateOverride = Config::FrameRateOverride::Disabled, + .frameRateMultipleThreshold = 0, + .idleTimerTimeout = 0ms, + .kernelIdleTimerController = {}}); RefreshRateSelector(const RefreshRateSelector&) = delete; RefreshRateSelector& operator=(const RefreshRateSelector&) = delete; @@ -302,7 +307,9 @@ public: // refresh rates. KernelIdleTimerAction getIdleTimerAction() const; - bool supportsFrameRateOverrideByContent() const { return mSupportsFrameRateOverrideByContent; } + bool supportsFrameRateOverrideByContent() const { + return mFrameRateOverrideConfig != Config::FrameRateOverride::Disabled; + } // Return the display refresh rate divisor to match the layer // frame rate, or 0 if the display refresh rate is not a multiple of the @@ -455,7 +462,7 @@ private: const std::vector<Fps> mKnownFrameRates; const Config mConfig; - bool mSupportsFrameRateOverrideByContent; + Config::FrameRateOverride mFrameRateOverrideConfig; struct GetRankedRefreshRatesCache { std::pair<std::vector<LayerRequirement>, GlobalSignals> arguments; diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h index bd4f40989d..31b1d6901c 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h @@ -68,6 +68,15 @@ struct FpsRange { bool includes(Fps) const; }; +struct FpsRanges { + // The range of refresh rates that refers to the display mode setting. + FpsRange physical; + + // the range of frame rates that refers to the render rate, which is + // the rate that frames are swapped. + FpsRange render; +}; + static_assert(std::is_trivially_copyable_v<Fps>); constexpr Fps operator""_Hz(unsigned long long frequency) { @@ -127,8 +136,24 @@ inline bool operator!=(FpsRange lhs, FpsRange rhs) { return !(lhs == rhs); } +inline bool operator==(const FpsRanges& lhs, const FpsRanges& rhs) { + return lhs.physical == rhs.physical && lhs.render == rhs.render; +} + +inline bool operator!=(const FpsRanges& lhs, const FpsRanges& rhs) { + return !(lhs == rhs); +} + +inline unsigned operator/(Fps lhs, Fps rhs) { + return static_cast<unsigned>(std::ceil(lhs.getValue() / rhs.getValue())); +} + } // namespace fps_approx_ops +constexpr Fps operator/(Fps fps, unsigned divisor) { + return Fps::fromPeriodNsecs(fps.getPeriodNsecs() * static_cast<nsecs_t>(divisor)); +} + inline bool FpsRange::includes(Fps fps) const { using fps_approx_ops::operator<=; return min <= fps && fps <= max; @@ -151,4 +176,10 @@ inline std::string to_string(FpsRange range) { return base::StringPrintf("[%s, %s]", to_string(min).c_str(), to_string(max).c_str()); } +inline std::string to_string(FpsRanges ranges) { + const auto& [physical, render] = ranges; + return base::StringPrintf("{physical=%s, render=%s}", to_string(physical).c_str(), + to_string(render).c_str()); +} + } // namespace android diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index aa930bcff3..6a25104a95 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -357,9 +357,6 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI // debugging stuff... char value[PROPERTY_VALUE_MAX]; - property_get("ro.bq.gpu_to_cpu_unsupported", value, "0"); - mGpuToCpuSupported = !atoi(value); - property_get("ro.build.type", value, "user"); mIsUserBuild = strcmp(value, "user") == 0; @@ -1132,8 +1129,8 @@ status_t SurfaceFlinger::setActiveModeFromBackdoor(const sp<display::DisplayToke display->refreshRateSelector().getCurrentPolicy().allowGroupSwitching; const scheduler::RefreshRateSelector::DisplayManagerPolicy policy{modeId, - allowGroupSwitching, - {fps, fps}}; + {fps, fps}, + allowGroupSwitching}; return setDesiredDisplayModeSpecsInternal(display, policy); }); @@ -2777,8 +2774,21 @@ sp<DisplayDevice> SurfaceFlinger::setupNewDisplayDeviceInternal( const auto [kernelIdleTimerController, idleTimerTimeoutMs] = getKernelIdleTimerProperties(compositionDisplay->getId()); + const auto enableFrameRateOverride = [&] { + using Config = scheduler::RefreshRateSelector::Config; + if (!sysprop::enable_frame_rate_override(false)) { + return Config::FrameRateOverride::Disabled; + } + + if (sysprop::frame_rate_override_for_native_rates(true)) { + return Config::FrameRateOverride::EnabledForNativeRefreshRates; + } + + return Config::FrameRateOverride::Enabled; + }(); + scheduler::RefreshRateSelector::Config config = - {.enableFrameRateOverride = android::sysprop::enable_frame_rate_override(false), + {.enableFrameRateOverride = enableFrameRateOverride, .frameRateMultipleThreshold = base::GetIntProperty("debug.sf.frame_rate_multiple_threshold", 0), .idleTimerTimeout = idleTimerTimeoutMs, @@ -5018,8 +5028,22 @@ void SurfaceFlinger::dumpWideColorInfo(std::string& result) const { } LayersProto SurfaceFlinger::dumpDrawingStateProto(uint32_t traceFlags) const { + std::unordered_set<uint64_t> stackIdsToSkip; + + // Determine if virtual layers display should be skipped + if ((traceFlags & LayerTracing::TRACE_VIRTUAL_DISPLAYS) == 0) { + for (const auto& [_, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) { + if (display->isVirtual()) { + stackIdsToSkip.insert(display->getLayerStack().id); + } + } + } + LayersProto layersProto; for (const sp<Layer>& layer : mDrawingState.layersSortedByZ) { + if (stackIdsToSkip.find(layer->getLayerStack().id) != stackIdsToSkip.end()) { + continue; + } layer->writeToProto(layersProto, traceFlags); } @@ -5181,10 +5205,7 @@ void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& comp StringAppendF(&result, " orientation=%s, isPoweredOn=%d\n", toCString(display->getOrientation()), display->isPoweredOn()); } - StringAppendF(&result, - " transaction-flags : %08x\n" - " gpu_to_cpu_unsupported : %d\n", - mTransactionFlags.load(), !mGpuToCpuSupported); + StringAppendF(&result, " transaction-flags : %08x\n", mTransactionFlags.load()); if (const auto display = getDefaultDisplayDeviceLocked()) { std::string fps, xDpi, yDpi; @@ -6525,7 +6546,6 @@ ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl( // Use an empty fence for the buffer fence, since we just created the buffer so // there is no need for synchronization with the GPU. base::unique_fd bufferFence; - getRenderEngine().useProtectedContext(useProtected); constexpr bool kUseFramebufferCache = false; const auto future = getRenderEngine() @@ -6537,9 +6557,6 @@ ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl( layer->onLayerDisplayed(future); } - // Always switch back to unprotected context. - getRenderEngine().useProtectedContext(false); - return future; } @@ -6653,10 +6670,33 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( return NO_ERROR; } -status_t SurfaceFlinger::setDesiredDisplayModeSpecs( - const sp<IBinder>& displayToken, ui::DisplayModeId defaultMode, bool allowGroupSwitching, - float primaryRefreshRateMin, float primaryRefreshRateMax, float appRequestRefreshRateMin, - float appRequestRefreshRateMax) { +namespace { +FpsRange translate(const gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange& aidlRange) { + return FpsRange{Fps::fromValue(aidlRange.min), Fps::fromValue(aidlRange.max)}; +} + +FpsRanges translate(const gui::DisplayModeSpecs::RefreshRateRanges& aidlRanges) { + return FpsRanges{translate(aidlRanges.physical), translate(aidlRanges.render)}; +} + +gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange translate(const FpsRange& range) { + gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange aidlRange; + aidlRange.min = range.min.getValue(); + aidlRange.max = range.max.getValue(); + return aidlRange; +} + +gui::DisplayModeSpecs::RefreshRateRanges translate(const FpsRanges& ranges) { + gui::DisplayModeSpecs::RefreshRateRanges aidlRanges; + aidlRanges.physical = translate(ranges.physical); + aidlRanges.render = translate(ranges.render); + return aidlRanges; +} + +} // namespace + +status_t SurfaceFlinger::setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, + const gui::DisplayModeSpecs& specs) { ATRACE_CALL(); if (!displayToken) { @@ -6674,12 +6714,8 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecs( return INVALID_OPERATION; } else { using Policy = scheduler::RefreshRateSelector::DisplayManagerPolicy; - const Policy policy{DisplayModeId(defaultMode), - allowGroupSwitching, - {Fps::fromValue(primaryRefreshRateMin), - Fps::fromValue(primaryRefreshRateMax)}, - {Fps::fromValue(appRequestRefreshRateMin), - Fps::fromValue(appRequestRefreshRateMax)}}; + const Policy policy{DisplayModeId(specs.defaultMode), translate(specs.primaryRanges), + translate(specs.appRequestRanges), specs.allowGroupSwitching}; return setDesiredDisplayModeSpecsInternal(display, policy); } @@ -6689,16 +6725,10 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecs( } status_t SurfaceFlinger::getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, - ui::DisplayModeId* outDefaultMode, - bool* outAllowGroupSwitching, - float* outPrimaryRefreshRateMin, - float* outPrimaryRefreshRateMax, - float* outAppRequestRefreshRateMin, - float* outAppRequestRefreshRateMax) { + gui::DisplayModeSpecs* outSpecs) { ATRACE_CALL(); - if (!displayToken || !outDefaultMode || !outPrimaryRefreshRateMin || - !outPrimaryRefreshRateMax || !outAppRequestRefreshRateMin || !outAppRequestRefreshRateMax) { + if (!displayToken || !outSpecs) { return BAD_VALUE; } @@ -6714,12 +6744,10 @@ status_t SurfaceFlinger::getDesiredDisplayModeSpecs(const sp<IBinder>& displayTo scheduler::RefreshRateSelector::Policy policy = display->refreshRateSelector().getDisplayManagerPolicy(); - *outDefaultMode = policy.defaultMode.value(); - *outAllowGroupSwitching = policy.allowGroupSwitching; - *outPrimaryRefreshRateMin = policy.primaryRange.min.getValue(); - *outPrimaryRefreshRateMax = policy.primaryRange.max.getValue(); - *outAppRequestRefreshRateMin = policy.appRequestRange.min.getValue(); - *outAppRequestRefreshRateMax = policy.appRequestRange.max.getValue(); + outSpecs->defaultMode = policy.defaultMode.value(); + outSpecs->allowGroupSwitching = policy.allowGroupSwitching; + outSpecs->primaryRanges = translate(policy.primaryRanges); + outSpecs->appRequestRanges = translate(policy.appRequestRanges); return NO_ERROR; } @@ -7610,18 +7638,11 @@ binder::Status SurfaceComposerAIDL::removeTunnelModeEnabledListener( return binderStatusFromStatusT(status); } -binder::Status SurfaceComposerAIDL::setDesiredDisplayModeSpecs( - const sp<IBinder>& displayToken, int32_t defaultMode, bool allowGroupSwitching, - float primaryRefreshRateMin, float primaryRefreshRateMax, float appRequestRefreshRateMin, - float appRequestRefreshRateMax) { +binder::Status SurfaceComposerAIDL::setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, + const gui::DisplayModeSpecs& specs) { status_t status = checkAccessPermission(); if (status == OK) { - status = mFlinger->setDesiredDisplayModeSpecs(displayToken, - static_cast<ui::DisplayModeId>(defaultMode), - allowGroupSwitching, primaryRefreshRateMin, - primaryRefreshRateMax, - appRequestRefreshRateMin, - appRequestRefreshRateMax); + status = mFlinger->setDesiredDisplayModeSpecs(displayToken, specs); } return binderStatusFromStatusT(status); } @@ -7637,25 +7658,7 @@ binder::Status SurfaceComposerAIDL::getDesiredDisplayModeSpecs(const sp<IBinder> return binderStatusFromStatusT(status); } - ui::DisplayModeId displayModeId; - bool allowGroupSwitching; - float primaryRefreshRateMin; - float primaryRefreshRateMax; - float appRequestRefreshRateMin; - float appRequestRefreshRateMax; - status = mFlinger->getDesiredDisplayModeSpecs(displayToken, &displayModeId, - &allowGroupSwitching, &primaryRefreshRateMin, - &primaryRefreshRateMax, &appRequestRefreshRateMin, - &appRequestRefreshRateMax); - if (status == NO_ERROR) { - outSpecs->defaultMode = displayModeId; - outSpecs->allowGroupSwitching = allowGroupSwitching; - outSpecs->primaryRefreshRateMin = primaryRefreshRateMin; - outSpecs->primaryRefreshRateMax = primaryRefreshRateMax; - outSpecs->appRequestRefreshRateMin = appRequestRefreshRateMin; - outSpecs->appRequestRefreshRateMax = appRequestRefreshRateMax; - } - + status = mFlinger->getDesiredDisplayModeSpecs(displayToken, outSpecs); return binderStatusFromStatusT(status); } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index d2907626e0..3d93d90b30 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -551,17 +551,8 @@ private: status_t addTunnelModeEnabledListener(const sp<gui::ITunnelModeEnabledListener>& listener); status_t removeTunnelModeEnabledListener(const sp<gui::ITunnelModeEnabledListener>& listener); status_t setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, - ui::DisplayModeId displayModeId, bool allowGroupSwitching, - float primaryRefreshRateMin, float primaryRefreshRateMax, - float appRequestRefreshRateMin, - float appRequestRefreshRateMax); - status_t getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, - ui::DisplayModeId* outDefaultMode, - bool* outAllowGroupSwitching, - float* outPrimaryRefreshRateMin, - float* outPrimaryRefreshRateMax, - float* outAppRequestRefreshRateMin, - float* outAppRequestRefreshRateMax); + const gui::DisplayModeSpecs&); + status_t getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, gui::DisplayModeSpecs*); status_t getDisplayBrightnessSupport(const sp<IBinder>& displayToken, bool* outSupport) const; status_t setDisplayBrightness(const sp<IBinder>& displayToken, const gui::DisplayBrightness& brightness); @@ -1109,7 +1100,6 @@ private: // constant members (no synchronization needed for access) const nsecs_t mBootTime = systemTime(); - bool mGpuToCpuSupported = false; bool mIsUserBuild = true; // Can only accessed from the main thread, these members @@ -1451,11 +1441,8 @@ public: const sp<gui::ITunnelModeEnabledListener>& listener) override; binder::Status removeTunnelModeEnabledListener( const sp<gui::ITunnelModeEnabledListener>& listener) override; - binder::Status setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, int32_t defaultMode, - bool allowGroupSwitching, float primaryRefreshRateMin, - float primaryRefreshRateMax, - float appRequestRefreshRateMin, - float appRequestRefreshRateMax) override; + binder::Status setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, + const gui::DisplayModeSpecs&) override; binder::Status getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, gui::DisplayModeSpecs* outSpecs) override; binder::Status getDisplayBrightnessSupport(const sp<IBinder>& displayToken, diff --git a/services/surfaceflinger/SurfaceFlingerProperties.cpp b/services/surfaceflinger/SurfaceFlingerProperties.cpp index 20fa091730..c8c71dfad1 100644 --- a/services/surfaceflinger/SurfaceFlingerProperties.cpp +++ b/services/surfaceflinger/SurfaceFlingerProperties.cpp @@ -367,6 +367,10 @@ bool enable_frame_rate_override(bool defaultValue) { return SurfaceFlingerProperties::enable_frame_rate_override().value_or(defaultValue); } +bool frame_rate_override_for_native_rates(bool defaultValue) { + return SurfaceFlingerProperties::frame_rate_override_for_native_rates().value_or(defaultValue); +} + bool enable_layer_caching(bool defaultValue) { return SurfaceFlingerProperties::enable_layer_caching().value_or(defaultValue); } diff --git a/services/surfaceflinger/SurfaceFlingerProperties.h b/services/surfaceflinger/SurfaceFlingerProperties.h index 080feee686..5e316cfa0e 100644 --- a/services/surfaceflinger/SurfaceFlingerProperties.h +++ b/services/surfaceflinger/SurfaceFlingerProperties.h @@ -96,6 +96,8 @@ bool update_device_product_info_on_hotplug_reconnect(bool defaultValue); bool enable_frame_rate_override(bool defaultValue); +bool frame_rate_override_for_native_rates(bool defaultValue); + bool enable_layer_caching(bool defaultValue); bool enable_sdr_dimming(bool defaultValue); diff --git a/services/surfaceflinger/Tracing/LayerTracing.h b/services/surfaceflinger/Tracing/LayerTracing.h index e73dac62e4..b32001cc63 100644 --- a/services/surfaceflinger/Tracing/LayerTracing.h +++ b/services/surfaceflinger/Tracing/LayerTracing.h @@ -55,6 +55,7 @@ public: TRACE_EXTRA = 1 << 3, TRACE_HWC = 1 << 4, TRACE_BUFFERS = 1 << 5, + TRACE_VIRTUAL_DISPLAYS = 1 << 6, TRACE_ALL = TRACE_INPUT | TRACE_COMPOSITION | TRACE_EXTRA, }; void setTraceFlags(uint32_t flags); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h index 94de6e5274..9ba9b90b82 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h +++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h @@ -505,16 +505,8 @@ public: } void getDesiredDisplayModeSpecs(sp<IBinder> &display) { - ui::DisplayModeId outDefaultMode; - bool outAllowGroupSwitching; - float outPrimaryRefreshRateMin; - float outPrimaryRefreshRateMax; - float outAppRequestRefreshRateMin; - float outAppRequestRefreshRateMax; - mFlinger->getDesiredDisplayModeSpecs(display, &outDefaultMode, &outAllowGroupSwitching, - &outPrimaryRefreshRateMin, &outPrimaryRefreshRateMax, - &outAppRequestRefreshRateMin, - &outAppRequestRefreshRateMax); + gui::DisplayModeSpecs _; + mFlinger->getDesiredDisplayModeSpecs(display, &_); } void setVsyncConfig(FuzzedDataProvider *fdp) { diff --git a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop index bcbe21a483..28da81ff50 100644 --- a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop +++ b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop @@ -445,6 +445,18 @@ prop { prop_name: "ro.surface_flinger.enable_frame_rate_override" } +# Limits the frame rate override feature (enable_frame_rate_override) to override the refresh rate +# to native display refresh rates only. Before introducing this flag, native display refresh rates +# was the default behvaiour. With this flag we can control which behaviour we want explicitly. +# This flag is intoruduced as a fail-safe machanism and planned to be defaulted to false. +prop { + api_name: "frame_rate_override_for_native_rates" + type: Boolean + scope: Public + access: Readonly + prop_name: "ro.surface_flinger.frame_rate_override_for_native_rates" +} + # Enables Layer Caching prop { api_name: "enable_layer_caching" diff --git a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt index 348a462038..0dfb80e5df 100644 --- a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt +++ b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt @@ -61,6 +61,10 @@ props { prop_name: "ro.surface_flinger.force_hwc_copy_for_virtual_displays" } prop { + api_name: "frame_rate_override_for_native_rates" + prop_name: "ro.surface_flinger.frame_rate_override_for_native_rates" + } + prop { api_name: "has_HDR_display" prop_name: "ro.surface_flinger.has_HDR_display" } diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp index 4f04934d34..16768441f0 100644 --- a/services/surfaceflinger/tests/Credentials_test.cpp +++ b/services/surfaceflinger/tests/Credentials_test.cpp @@ -207,23 +207,12 @@ TEST_F(CredentialsTest, GetDisplayNativePrimariesTest) { TEST_F(CredentialsTest, SetDesiredDisplayConfigsTest) { const auto display = getFirstDisplayToken(); - ui::DisplayModeId defaultMode; - bool allowGroupSwitching; - float primaryFpsMin; - float primaryFpsMax; - float appRequestFpsMin; - float appRequestFpsMax; - status_t res = - SurfaceComposerClient::getDesiredDisplayModeSpecs(display, &defaultMode, - &allowGroupSwitching, &primaryFpsMin, - &primaryFpsMax, &appRequestFpsMin, - &appRequestFpsMax); + gui::DisplayModeSpecs specs; + status_t res = SurfaceComposerClient::getDesiredDisplayModeSpecs(display, &specs); ASSERT_EQ(res, NO_ERROR); + gui::DisplayModeSpecs setSpecs; std::function<status_t()> condition = [=]() { - return SurfaceComposerClient::setDesiredDisplayModeSpecs(display, defaultMode, - allowGroupSwitching, primaryFpsMin, - primaryFpsMax, appRequestFpsMin, - appRequestFpsMax); + return SurfaceComposerClient::setDesiredDisplayModeSpecs(display, specs); }; ASSERT_NO_FATAL_FAILURE(checkWithPrivileges<status_t>(condition, NO_ERROR, PERMISSION_DENIED)); } diff --git a/services/surfaceflinger/tests/DisplayConfigs_test.cpp b/services/surfaceflinger/tests/DisplayConfigs_test.cpp index 02c934e576..10dae4636e 100644 --- a/services/surfaceflinger/tests/DisplayConfigs_test.cpp +++ b/services/surfaceflinger/tests/DisplayConfigs_test.cpp @@ -39,37 +39,19 @@ namespace android { */ class RefreshRateRangeTest : public ::testing::Test { private: - ui::DisplayModeId initialDefaultMode; - bool initialAllowGroupSwitching; - float initialPrimaryMin; - float initialPrimaryMax; - float initialAppRequestMin; - float initialAppRequestMax; + gui::DisplayModeSpecs mSpecs; protected: void SetUp() override { const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); ASSERT_FALSE(ids.empty()); mDisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); - status_t res = - SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, - &initialDefaultMode, - &initialAllowGroupSwitching, - &initialPrimaryMin, - &initialPrimaryMax, - &initialAppRequestMin, - &initialAppRequestMax); + status_t res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &mSpecs); ASSERT_EQ(res, NO_ERROR); } void TearDown() override { - status_t res = - SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, initialDefaultMode, - initialAllowGroupSwitching, - initialPrimaryMin, - initialPrimaryMax, - initialAppRequestMin, - initialAppRequestMax); + status_t res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, mSpecs); ASSERT_EQ(res, NO_ERROR); } @@ -85,61 +67,39 @@ TEST_F(RefreshRateRangeTest, setAllConfigs) { ASSERT_EQ(res, NO_ERROR); ASSERT_GT(modes.size(), 0); + gui::DisplayModeSpecs setSpecs; + setSpecs.allowGroupSwitching = false; for (size_t i = 0; i < modes.size(); i++) { - res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, modes[i].id, false, - modes[i].refreshRate, - modes[i].refreshRate, - modes[i].refreshRate, - modes[i].refreshRate); + setSpecs.defaultMode = modes[i].id; + setSpecs.primaryRanges.physical.min = modes[i].refreshRate; + setSpecs.primaryRanges.physical.max = modes[i].refreshRate; + setSpecs.primaryRanges.render = setSpecs.primaryRanges.physical; + setSpecs.appRequestRanges = setSpecs.primaryRanges; + res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, setSpecs); ASSERT_EQ(res, NO_ERROR); - ui::DisplayModeId defaultConfig; - bool allowGroupSwitching; - float primaryRefreshRateMin; - float primaryRefreshRateMax; - float appRequestRefreshRateMin; - float appRequestRefreshRateMax; - res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &defaultConfig, - &allowGroupSwitching, - &primaryRefreshRateMin, - &primaryRefreshRateMax, - &appRequestRefreshRateMin, - &appRequestRefreshRateMax); + gui::DisplayModeSpecs getSpecs; + res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &getSpecs); ASSERT_EQ(res, NO_ERROR); - ASSERT_EQ(defaultConfig, i); - ASSERT_EQ(allowGroupSwitching, false); - ASSERT_EQ(primaryRefreshRateMin, modes[i].refreshRate); - ASSERT_EQ(primaryRefreshRateMax, modes[i].refreshRate); - ASSERT_EQ(appRequestRefreshRateMin, modes[i].refreshRate); - ASSERT_EQ(appRequestRefreshRateMax, modes[i].refreshRate); + ASSERT_EQ(setSpecs, getSpecs); } } void RefreshRateRangeTest::testSetAllowGroupSwitching(bool allowGroupSwitching) { - status_t res = - SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, 0, allowGroupSwitching, - 0.f, 90.f, 0.f, 90.f); + gui::DisplayModeSpecs setSpecs; + setSpecs.defaultMode = 0; + setSpecs.allowGroupSwitching = allowGroupSwitching; + setSpecs.primaryRanges.physical.min = 0; + setSpecs.primaryRanges.physical.max = 90; + setSpecs.primaryRanges.render = setSpecs.primaryRanges.physical; + setSpecs.appRequestRanges = setSpecs.primaryRanges; + + status_t res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, setSpecs); ASSERT_EQ(res, NO_ERROR); - ui::DisplayModeId defaultConfig; - bool newAllowGroupSwitching; - float primaryRefreshRateMin; - float primaryRefreshRateMax; - float appRequestRefreshRateMin; - float appRequestRefreshRateMax; - - res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &defaultConfig, - &newAllowGroupSwitching, - &primaryRefreshRateMin, - &primaryRefreshRateMax, - &appRequestRefreshRateMin, - &appRequestRefreshRateMax); + gui::DisplayModeSpecs getSpecs; + res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &getSpecs); ASSERT_EQ(res, NO_ERROR); - ASSERT_EQ(defaultConfig, 0); - ASSERT_EQ(newAllowGroupSwitching, allowGroupSwitching); - ASSERT_EQ(primaryRefreshRateMin, 0.f); - ASSERT_EQ(primaryRefreshRateMax, 90.f); - ASSERT_EQ(appRequestRefreshRateMin, 0.f); - ASSERT_EQ(appRequestRefreshRateMax, 90.f); + ASSERT_EQ(setSpecs, getSpecs); } TEST_F(RefreshRateRangeTest, setAllowGroupSwitching) { diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp index e7ae53c01a..cedb7eb6ee 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp @@ -37,6 +37,7 @@ namespace android::scheduler { namespace hal = android::hardware::graphics::composer::hal; +using Config = RefreshRateSelector::Config; using LayerRequirement = RefreshRateSelector::LayerRequirement; using LayerVoteType = RefreshRateSelector::LayerVoteType; using SetPolicyResult = RefreshRateSelector::SetPolicyResult; @@ -357,7 +358,7 @@ TEST_F(RefreshRateSelectorTest, getBestRefreshRate_noLayers) { constexpr bool kAllowGroupSwitching = true; EXPECT_EQ(SetPolicyResult::Changed, selector.setDisplayManagerPolicy( - {kModeId90, kAllowGroupSwitching, {0_Hz, 90_Hz}})); + {kModeId90, {0_Hz, 90_Hz}, kAllowGroupSwitching})); EXPECT_EQ(kMode90_G1, selector.getBestRefreshRate()); } } @@ -1105,7 +1106,7 @@ TEST_F(RefreshRateSelectorTest, getMinRefreshRatesByPolicyOutsideTheGroup) { TestableRefreshRateSelector selector(kModes_30_60_90, kModeId72); EXPECT_EQ(SetPolicyResult::Changed, - selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}})); const auto refreshRates = selector.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt, RefreshRateOrder::Ascending); @@ -1126,7 +1127,7 @@ TEST_F(RefreshRateSelectorTest, getMaxRefreshRatesByPolicyOutsideTheGroup) { TestableRefreshRateSelector selector(kModes_30_60_90, kModeId72); EXPECT_EQ(SetPolicyResult::Changed, - selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}})); const auto refreshRates = selector.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt, RefreshRateOrder::Descending); @@ -1351,8 +1352,10 @@ TEST_F(RefreshRateSelectorTest, getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresTouchFlag) { TestableRefreshRateSelector selector(kModes_60_90, kModeId90); + constexpr FpsRange k90 = {90_Hz, 90_Hz}; + constexpr FpsRange k60_90 = {60_Hz, 90_Hz}; EXPECT_EQ(SetPolicyResult::Changed, - selector.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId90, {k90, k90}, {k60_90, k60_90}})); std::vector<LayerRequirement> layers = {{.weight = 1.f}}; auto& lr = layers[0]; @@ -1373,8 +1376,11 @@ TEST_F(RefreshRateSelectorTest, getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresIdleFlag) { TestableRefreshRateSelector selector(kModes_60_90, kModeId60); + constexpr FpsRange k60 = {60_Hz, 60_Hz}; + constexpr FpsRange k60_90 = {60_Hz, 90_Hz}; + EXPECT_EQ(SetPolicyResult::Changed, - selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {k60, k60}, {k60_90, k60_90}})); std::vector<LayerRequirement> layers = {{.weight = 1.f}}; auto& lr = layers[0]; @@ -1513,8 +1519,11 @@ TEST_F(RefreshRateSelectorTest, getBestRefreshRate_withDisplayManagerRequestingSingleRate_onlySwitchesRatesForExplicitFocusedLayers) { TestableRefreshRateSelector selector(kModes_60_90, kModeId90); + constexpr FpsRange k90 = {90_Hz, 90_Hz}; + constexpr FpsRange k60_90 = {60_Hz, 90_Hz}; + EXPECT_EQ(SetPolicyResult::Changed, - selector.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId90, {k90, k90}, {k60_90, k60_90}})); const auto [ranking, signals] = selector.getRankedRefreshRates({}, {}); EXPECT_EQ(ranking.front().modePtr, kMode90); @@ -1849,8 +1858,11 @@ TEST_F(RefreshRateSelectorTest, primaryVsAppRequestPolicy) { return selector.getBestRefreshRate(layers, {.touch = args.touch})->getId(); }; + constexpr FpsRange k30_60 = {30_Hz, 60_Hz}; + constexpr FpsRange k30_90 = {30_Hz, 90_Hz}; + EXPECT_EQ(SetPolicyResult::Changed, - selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 60_Hz}, {30_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {k30_60, k30_60}, {k30_90, k30_90}})); EXPECT_EQ(kModeId60, selector.getBestRefreshRate()->getId()); EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz)); @@ -1875,7 +1887,7 @@ TEST_F(RefreshRateSelectorTest, primaryVsAppRequestPolicy) { getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz, {.touch = true})); EXPECT_EQ(SetPolicyResult::Changed, - selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 60_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}})); EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz)); EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Min, 90_Hz)); @@ -1904,7 +1916,7 @@ TEST_F(RefreshRateSelectorTest, idle) { }; EXPECT_EQ(SetPolicyResult::Changed, - selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}, {60_Hz, 90_Hz}})); + selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}})); // Idle should be lower priority than touch boost. { @@ -2023,7 +2035,8 @@ TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExact) { TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExactEnableFrameRateOverride) { TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60, - {.enableFrameRateOverride = true}); + {.enableFrameRateOverride = + Config::FrameRateOverride::Enabled}); std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}}; auto& explicitExactLayer = layers[0]; @@ -2089,7 +2102,8 @@ TEST_F(RefreshRateSelectorTest, getBestRefreshRate_WritesCache) { TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExactTouchBoost) { TestableRefreshRateSelector selector(kModes_60_120, kModeId60, - {.enableFrameRateOverride = true}); + {.enableFrameRateOverride = + Config::FrameRateOverride::Enabled}); std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}}; auto& explicitExactLayer = layers[0]; @@ -2114,7 +2128,8 @@ TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExactTouchBoost) { TEST_F(RefreshRateSelectorTest, getBestRefreshRate_FractionalRefreshRates_ExactAndDefault) { TestableRefreshRateSelector selector(kModes_24_25_30_50_60_Frac, kModeId60, - {.enableFrameRateOverride = true}); + {.enableFrameRateOverride = + Config::FrameRateOverride::Enabled}); std::vector<LayerRequirement> layers = {{.weight = 0.5f}, {.weight = 0.5f}}; auto& explicitDefaultLayer = layers[0]; @@ -2308,7 +2323,7 @@ TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_noLayers) { TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_60on120) { RefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120, - {.enableFrameRateOverride = true}); + {.enableFrameRateOverride = Config::FrameRateOverride::Enabled}); std::vector<LayerRequirement> layers = {{.weight = 1.f}}; layers[0].name = "Test layer"; @@ -2346,7 +2361,7 @@ TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_60on120) { TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_twoUids) { RefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120, - {.enableFrameRateOverride = true}); + {.enableFrameRateOverride = Config::FrameRateOverride::Enabled}); std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f}, {.ownerUid = 5678, .weight = 1.f}}; @@ -2379,7 +2394,7 @@ TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_twoUids) { TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_touch) { RefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120, - {.enableFrameRateOverride = true}); + {.enableFrameRateOverride = Config::FrameRateOverride::Enabled}); std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f}}; layers[0].name = "Test layer"; @@ -2417,5 +2432,139 @@ TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_touch) { EXPECT_TRUE(frameRateOverrides.empty()); } +TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_DivisorIsNotDisplayRefreshRate_Enabled) { + RefreshRateSelector selector(kModes_60_120, kModeId120, + {.enableFrameRateOverride = Config::FrameRateOverride::Enabled}); + + std::vector<LayerRequirement> layers = {{.weight = 1.f}}; + layers[0].name = "Test layer"; + layers[0].ownerUid = 1234; + layers[0].desiredRefreshRate = 30_Hz; + layers[0].vote = LayerVoteType::ExplicitDefault; + + auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_EQ(1u, frameRateOverrides.size()); + ASSERT_EQ(1u, frameRateOverrides.count(1234)); + EXPECT_EQ(30_Hz, frameRateOverrides.at(1234)); + + layers[0].vote = LayerVoteType::ExplicitExactOrMultiple; + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_EQ(1u, frameRateOverrides.size()); + ASSERT_EQ(1u, frameRateOverrides.count(1234)); + EXPECT_EQ(30_Hz, frameRateOverrides.at(1234)); + + layers[0].vote = LayerVoteType::NoVote; + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_TRUE(frameRateOverrides.empty()); + + layers[0].vote = LayerVoteType::Min; + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_TRUE(frameRateOverrides.empty()); + + layers[0].vote = LayerVoteType::Max; + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_TRUE(frameRateOverrides.empty()); + + layers[0].vote = LayerVoteType::Heuristic; + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_TRUE(frameRateOverrides.empty()); +} + +TEST_F(RefreshRateSelectorTest, + getFrameRateOverrides_DivisorIsNotDisplayRefreshRate_EnabledForNativeRefreshRates) { + RefreshRateSelector selector(kModes_60_120, kModeId120, + {.enableFrameRateOverride = + Config::FrameRateOverride::EnabledForNativeRefreshRates}); + + std::vector<LayerRequirement> layers = {{.weight = 1.f}}; + layers[0].name = "Test layer"; + layers[0].ownerUid = 1234; + layers[0].desiredRefreshRate = 30_Hz; + layers[0].vote = LayerVoteType::ExplicitDefault; + + auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_EQ(1u, frameRateOverrides.size()); + ASSERT_EQ(1u, frameRateOverrides.count(1234)); + EXPECT_EQ(60_Hz, frameRateOverrides.at(1234)); + + layers[0].vote = LayerVoteType::ExplicitExactOrMultiple; + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_EQ(1u, frameRateOverrides.size()); + ASSERT_EQ(1u, frameRateOverrides.count(1234)); + EXPECT_EQ(60_Hz, frameRateOverrides.at(1234)); + + layers[0].vote = LayerVoteType::NoVote; + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_TRUE(frameRateOverrides.empty()); + + layers[0].vote = LayerVoteType::Min; + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_TRUE(frameRateOverrides.empty()); + + layers[0].vote = LayerVoteType::Max; + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_TRUE(frameRateOverrides.empty()); + + layers[0].vote = LayerVoteType::Heuristic; + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_TRUE(frameRateOverrides.empty()); +} + +TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_InPolicy) { + TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120, + {.enableFrameRateOverride = + Config::FrameRateOverride::Enabled}); + + std::vector<LayerRequirement> layers = {{.weight = 1.f}}; + { + const FpsRange physical = {120_Hz, 120_Hz}; + const FpsRange render = {60_Hz, 90_Hz}; + EXPECT_EQ(SetPolicyResult::Changed, + selector.setDisplayManagerPolicy( + {kModeId120, {physical, render}, {physical, render}})); + } + + layers[0].name = "30Hz"; + layers[0].ownerUid = 1234; + layers[0].desiredRefreshRate = 30_Hz; + layers[0].vote = LayerVoteType::ExplicitDefault; + + auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_EQ(1u, frameRateOverrides.size()); + EXPECT_EQ(1u, frameRateOverrides.count(1234)); + EXPECT_EQ(60_Hz, frameRateOverrides.at(1234)); + + { + const FpsRange physical = {120_Hz, 120_Hz}; + const FpsRange render = {30_Hz, 90_Hz}; + EXPECT_EQ(SetPolicyResult::Changed, + selector.setDisplayManagerPolicy( + {kModeId120, {physical, render}, {physical, render}})); + } + + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_EQ(1u, frameRateOverrides.size()); + EXPECT_EQ(1u, frameRateOverrides.count(1234)); + EXPECT_EQ(30_Hz, frameRateOverrides.at(1234)); + + { + const FpsRange physical = {120_Hz, 120_Hz}; + const FpsRange render = {30_Hz, 30_Hz}; + EXPECT_EQ(SetPolicyResult::Changed, + selector.setDisplayManagerPolicy( + {kModeId120, {physical, render}, {physical, render}})); + } + + layers[0].name = "60Hz"; + layers[0].ownerUid = 1234; + layers[0].desiredRefreshRate = 60_Hz; + layers[0].vote = LayerVoteType::ExplicitDefault; + + frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {}); + EXPECT_EQ(1u, frameRateOverrides.size()); + EXPECT_EQ(1u, frameRateOverrides.count(1234)); + EXPECT_EQ(30_Hz, frameRateOverrides.at(1234)); +} + } // namespace } // namespace android::scheduler diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp index 4c25463e6e..05d0ebf773 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "mock/MockDisplayModeSpecs.h" #include "mock/MockEventThread.h" #undef LOG_TAG #define LOG_TAG "LibSurfaceFlingerUnittests" @@ -119,8 +120,9 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithRefreshRe mFlinger.onActiveDisplayChanged(mDisplay); - mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90.value(), - false, 0.f, 120.f, 0.f, 120.f); + mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), + mock::createDisplayModeSpecs(kModeId90.value(), false, 0, + 120)); ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90); @@ -157,8 +159,9 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithoutRefres mFlinger.onActiveDisplayChanged(mDisplay); - mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90.value(), - true, 0.f, 120.f, 0.f, 120.f); + mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), + mock::createDisplayModeSpecs(kModeId90.value(), true, 0, + 120)); ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90); @@ -191,8 +194,9 @@ TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) { mFlinger.onActiveDisplayChanged(mDisplay); - mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90.value(), - false, 0.f, 120.f, 0.f, 120.f); + mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), + mock::createDisplayModeSpecs(kModeId90.value(), false, 0, + 120)); const VsyncPeriodChangeTimeline timeline{.refreshRequired = true}; EXPECT_CALL(*mComposer, @@ -202,8 +206,9 @@ TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) { mFlinger.commit(); - mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId120.value(), - false, 0.f, 180.f, 0.f, 180.f); + mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), + mock::createDisplayModeSpecs(kModeId120.value(), false, 0, + 180)); ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId120); @@ -232,8 +237,9 @@ TEST_F(DisplayModeSwitchingTest, changeResolution_OnActiveDisplay_WithoutRefresh mFlinger.onActiveDisplayChanged(mDisplay); - mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90_4K.value(), - false, 0.f, 120.f, 0.f, 120.f); + mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), + mock::createDisplayModeSpecs(kModeId90_4K.value(), false, 0, + 120)); ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value()); ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90_4K); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 35c037c051..46eca69c6a 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -455,14 +455,9 @@ public: return SurfaceFlinger::calculateMaxAcquiredBufferCount(refreshRate, presentLatency); } - auto setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, ui::DisplayModeId defaultMode, - bool allowGroupSwitching, float primaryRefreshRateMin, - float primaryRefreshRateMax, float appRequestRefreshRateMin, - float appRequestRefreshRateMax) { - return mFlinger->setDesiredDisplayModeSpecs(displayToken, defaultMode, allowGroupSwitching, - primaryRefreshRateMin, primaryRefreshRateMax, - appRequestRefreshRateMin, - appRequestRefreshRateMax); + auto setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, + const gui::DisplayModeSpecs& specs) { + return mFlinger->setDesiredDisplayModeSpecs(displayToken, specs); } void onActiveDisplayChanged(const sp<DisplayDevice>& activeDisplay) { diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h index 5f749dfbcc..f4ded216cb 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h @@ -42,6 +42,7 @@ public: MOCK_METHOD(Status, updateTargetWorkDuration, (int64_t), (override)); MOCK_METHOD(Status, reportActualWorkDuration, (const ::std::vector<WorkDuration>&), (override)); MOCK_METHOD(Status, sendHint, (SessionHint), (override)); + MOCK_METHOD(Status, setThreads, (const ::std::vector<int32_t>&), (override)); }; } // namespace android::Hwc2::mock diff --git a/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h b/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h new file mode 100644 index 0000000000..a71e82cc75 --- /dev/null +++ b/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#pragma once + +#include <android/gui/DisplayModeSpecs.h> + +namespace android::mock { + +inline gui::DisplayModeSpecs createDisplayModeSpecs(int32_t defaultMode, bool allowGroupSwitching, + float minFps, float maxFps) { + gui::DisplayModeSpecs specs; + specs.defaultMode = defaultMode; + specs.allowGroupSwitching = allowGroupSwitching; + specs.primaryRanges.physical.min = minFps; + specs.primaryRanges.physical.max = maxFps; + specs.primaryRanges.render = specs.primaryRanges.physical; + specs.appRequestRanges = specs.primaryRanges; + return specs; +} + +} // namespace android::mock |