You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
380 lines
15 KiB
380 lines
15 KiB
/*
|
|
* Copyright 2019 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <gtest/gtest.h>
|
|
#include <thread>
|
|
|
|
#include <binder/ProcessState.h>
|
|
#include <gui/DisplayEventReceiver.h>
|
|
#include <gui/IRegionSamplingListener.h>
|
|
#include <gui/ISurfaceComposer.h>
|
|
#include <gui/Surface.h>
|
|
#include <gui/SurfaceComposerClient.h>
|
|
#include <private/gui/ComposerService.h>
|
|
#include <utils/Looper.h>
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
namespace android::test {
|
|
|
|
struct ChoreographerSync {
|
|
ChoreographerSync(DisplayEventReceiver& receiver) : receiver_(receiver) {}
|
|
~ChoreographerSync() = default;
|
|
|
|
void notify() const {
|
|
std::unique_lock<decltype(mutex_)> lk(mutex_);
|
|
|
|
auto check_event = [](auto const& ev) -> bool {
|
|
return ev.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC;
|
|
};
|
|
DisplayEventReceiver::Event ev_;
|
|
int evs = receiver_.getEvents(&ev_, 1);
|
|
auto vsync_event_found = check_event(ev_);
|
|
while (evs) {
|
|
evs = receiver_.getEvents(&ev_, 1);
|
|
vsync_event_found |= check_event(ev_);
|
|
}
|
|
|
|
if (vsync_event_found) {
|
|
notification_arrived_ = true;
|
|
cv_.notify_all();
|
|
}
|
|
}
|
|
|
|
void wait_vsync_notify() const {
|
|
std::unique_lock<decltype(mutex_)> lk(mutex_);
|
|
cv_.wait(lk, [this] { return notification_arrived_; });
|
|
notification_arrived_ = false;
|
|
}
|
|
|
|
private:
|
|
ChoreographerSync(ChoreographerSync const&) = delete;
|
|
ChoreographerSync& operator=(ChoreographerSync const&) = delete;
|
|
|
|
std::mutex mutable mutex_;
|
|
std::condition_variable mutable cv_;
|
|
bool mutable notification_arrived_ = false;
|
|
DisplayEventReceiver& receiver_;
|
|
};
|
|
|
|
struct ChoreographerSim {
|
|
static std::unique_ptr<ChoreographerSim> make() {
|
|
auto receiver = std::make_unique<DisplayEventReceiver>();
|
|
if (!receiver || receiver->initCheck() == NO_INIT) {
|
|
ALOGE("No display reciever");
|
|
return nullptr;
|
|
}
|
|
return std::unique_ptr<ChoreographerSim>(new ChoreographerSim(std::move(receiver)));
|
|
}
|
|
|
|
~ChoreographerSim() {
|
|
poll_ = false;
|
|
looper->wake();
|
|
choreographer_thread_.join();
|
|
}
|
|
|
|
void request_render_wait(std::function<void()> const& render_fn) {
|
|
display_event_receiver_->requestNextVsync();
|
|
choreographer_.wait_vsync_notify();
|
|
render_fn();
|
|
|
|
// Purpose is to make sure that the content is latched by the time we sample.
|
|
// Waiting one vsync after queueing could still race with vsync, so wait for two, after
|
|
// which the content is pretty reliably on screen.
|
|
display_event_receiver_->requestNextVsync();
|
|
choreographer_.wait_vsync_notify();
|
|
display_event_receiver_->requestNextVsync();
|
|
choreographer_.wait_vsync_notify();
|
|
}
|
|
|
|
private:
|
|
ChoreographerSim(std::unique_ptr<DisplayEventReceiver> receiver)
|
|
: display_event_receiver_{std::move(receiver)},
|
|
choreographer_{*display_event_receiver_},
|
|
looper{new Looper(false)} {
|
|
choreographer_thread_ = std::thread([this] {
|
|
auto vsync_notify_fd = display_event_receiver_->getFd();
|
|
looper->addFd(vsync_notify_fd, 0, Looper::EVENT_INPUT,
|
|
[](int /*fd*/, int /*events*/, void* data) -> int {
|
|
if (!data) return 0;
|
|
reinterpret_cast<ChoreographerSync*>(data)->notify();
|
|
return 1;
|
|
},
|
|
const_cast<void*>(reinterpret_cast<void const*>(&choreographer_)));
|
|
|
|
while (poll_) {
|
|
auto const poll_interval =
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(1s).count();
|
|
auto rc = looper->pollOnce(poll_interval);
|
|
if ((rc != Looper::POLL_CALLBACK) && (rc != Looper::POLL_WAKE))
|
|
ALOGW("Vsync Looper returned: %i\n", rc);
|
|
}
|
|
});
|
|
}
|
|
|
|
ChoreographerSim(ChoreographerSim const&) = delete;
|
|
ChoreographerSim& operator=(ChoreographerSim const&) = delete;
|
|
|
|
std::unique_ptr<DisplayEventReceiver> const display_event_receiver_;
|
|
ChoreographerSync const choreographer_;
|
|
sp<Looper> looper;
|
|
std::thread choreographer_thread_;
|
|
std::atomic<bool> poll_{true};
|
|
};
|
|
|
|
struct Listener : BnRegionSamplingListener {
|
|
void onSampleCollected(float medianLuma) override {
|
|
std::unique_lock<decltype(mutex)> lk(mutex);
|
|
received = true;
|
|
mLuma = medianLuma;
|
|
cv.notify_all();
|
|
};
|
|
bool wait_event(std::chrono::milliseconds timeout) {
|
|
std::unique_lock<decltype(mutex)> lk(mutex);
|
|
return cv.wait_for(lk, timeout, [this] { return received; });
|
|
}
|
|
|
|
float luma() {
|
|
std::unique_lock<decltype(mutex)> lk(mutex);
|
|
return mLuma;
|
|
}
|
|
|
|
void reset() {
|
|
std::unique_lock<decltype(mutex)> lk(mutex);
|
|
received = false;
|
|
}
|
|
|
|
private:
|
|
std::condition_variable cv;
|
|
std::mutex mutex;
|
|
bool received = false;
|
|
float mLuma = -0.0f;
|
|
};
|
|
|
|
// Hoisted to TestSuite setup to avoid flake in test (b/124675919)
|
|
std::unique_ptr<ChoreographerSim> gChoreographerSim = nullptr;
|
|
|
|
struct RegionSamplingTest : ::testing::Test {
|
|
protected:
|
|
RegionSamplingTest() { ProcessState::self()->startThreadPool(); }
|
|
|
|
static void SetUpTestSuite() {
|
|
gChoreographerSim = ChoreographerSim::make();
|
|
ASSERT_NE(gChoreographerSim, nullptr);
|
|
}
|
|
|
|
void SetUp() override {
|
|
mSurfaceComposerClient = new SurfaceComposerClient;
|
|
ASSERT_EQ(NO_ERROR, mSurfaceComposerClient->initCheck());
|
|
|
|
mBackgroundLayer =
|
|
mSurfaceComposerClient->createSurface(String8("Background RegionSamplingTest"), 0,
|
|
0, PIXEL_FORMAT_RGBA_8888,
|
|
ISurfaceComposerClient::eFXSurfaceEffect);
|
|
uint32_t layerPositionBottom = 0x7E000000;
|
|
SurfaceComposerClient::Transaction{}
|
|
.setLayer(mBackgroundLayer, layerPositionBottom)
|
|
.setPosition(mBackgroundLayer, 100, 100)
|
|
.setColor(mBackgroundLayer, half3{0.5, 0.5, 0.5})
|
|
.show(mBackgroundLayer)
|
|
.apply();
|
|
|
|
mContentLayer = mSurfaceComposerClient->createSurface(String8("Content RegionSamplingTest"),
|
|
300, 300, PIXEL_FORMAT_RGBA_8888, 0);
|
|
|
|
SurfaceComposerClient::Transaction{}
|
|
.setLayer(mContentLayer, layerPositionBottom + 1)
|
|
.setPosition(mContentLayer, 100, 100)
|
|
.setColor(mContentLayer, half3{0.5, 0.5, 0.5})
|
|
.show(mContentLayer)
|
|
.apply();
|
|
|
|
mTopLayer = mSurfaceComposerClient->createSurface(String8("TopLayer RegionSamplingTest"), 0,
|
|
0, PIXEL_FORMAT_RGBA_8888, 0);
|
|
SurfaceComposerClient::Transaction{}
|
|
.setLayer(mTopLayer, layerPositionBottom + 2)
|
|
.setPosition(mTopLayer, 0, 0)
|
|
.show(mBackgroundLayer)
|
|
.apply();
|
|
}
|
|
|
|
void fill_render(uint32_t rgba_value) {
|
|
auto surface = mContentLayer->getSurface();
|
|
ANativeWindow_Buffer outBuffer;
|
|
status_t status = surface->lock(&outBuffer, NULL);
|
|
ASSERT_EQ(status, android::OK);
|
|
auto b = reinterpret_cast<uint32_t*>(outBuffer.bits);
|
|
for (auto i = 0; i < outBuffer.height; i++) {
|
|
for (auto j = 0; j < outBuffer.width; j++) {
|
|
b[j] = rgba_value;
|
|
}
|
|
b += outBuffer.stride;
|
|
}
|
|
|
|
gChoreographerSim->request_render_wait([&surface] { surface->unlockAndPost(); });
|
|
}
|
|
|
|
sp<SurfaceComposerClient> mSurfaceComposerClient;
|
|
sp<SurfaceControl> mBackgroundLayer;
|
|
sp<SurfaceControl> mContentLayer;
|
|
sp<SurfaceControl> mTopLayer;
|
|
|
|
uint32_t const rgba_green = 0xFF00FF00;
|
|
float const luma_green = 0.7152;
|
|
uint32_t const rgba_blue = 0xFFFF0000;
|
|
float const luma_blue = 0.0722;
|
|
float const error_margin = 0.01;
|
|
float const luma_gray = 0.50;
|
|
};
|
|
|
|
TEST_F(RegionSamplingTest, invalidLayerHandle_doesNotCrash) {
|
|
sp<ISurfaceComposer> composer = ComposerService::getComposerService();
|
|
sp<Listener> listener = new Listener();
|
|
const Rect sampleArea{100, 100, 200, 200};
|
|
// Passing in composer service as the layer handle should not crash, we'll
|
|
// treat it as a layer that no longer exists and silently allow sampling to
|
|
// occur.
|
|
status_t status = composer->addRegionSamplingListener(sampleArea,
|
|
IInterface::asBinder(composer), listener);
|
|
ASSERT_EQ(NO_ERROR, status);
|
|
composer->removeRegionSamplingListener(listener);
|
|
}
|
|
|
|
TEST_F(RegionSamplingTest, DISABLED_CollectsLuma) {
|
|
fill_render(rgba_green);
|
|
|
|
sp<ISurfaceComposer> composer = ComposerService::getComposerService();
|
|
sp<Listener> listener = new Listener();
|
|
const Rect sampleArea{100, 100, 200, 200};
|
|
composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener);
|
|
|
|
EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
|
|
EXPECT_NEAR(listener->luma(), luma_green, error_margin);
|
|
|
|
composer->removeRegionSamplingListener(listener);
|
|
}
|
|
|
|
TEST_F(RegionSamplingTest, DISABLED_CollectsChangingLuma) {
|
|
fill_render(rgba_green);
|
|
|
|
sp<ISurfaceComposer> composer = ComposerService::getComposerService();
|
|
sp<Listener> listener = new Listener();
|
|
const Rect sampleArea{100, 100, 200, 200};
|
|
composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener);
|
|
|
|
EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
|
|
EXPECT_NEAR(listener->luma(), luma_green, error_margin);
|
|
|
|
listener->reset();
|
|
|
|
fill_render(rgba_blue);
|
|
EXPECT_TRUE(listener->wait_event(300ms))
|
|
<< "timed out waiting for 2nd luma event to be received";
|
|
EXPECT_NEAR(listener->luma(), luma_blue, error_margin);
|
|
|
|
composer->removeRegionSamplingListener(listener);
|
|
}
|
|
|
|
TEST_F(RegionSamplingTest, DISABLED_CollectsLumaFromTwoRegions) {
|
|
fill_render(rgba_green);
|
|
sp<ISurfaceComposer> composer = ComposerService::getComposerService();
|
|
sp<Listener> greenListener = new Listener();
|
|
const Rect greenSampleArea{100, 100, 200, 200};
|
|
composer->addRegionSamplingListener(greenSampleArea, mTopLayer->getHandle(), greenListener);
|
|
|
|
sp<Listener> grayListener = new Listener();
|
|
const Rect graySampleArea{500, 100, 600, 200};
|
|
composer->addRegionSamplingListener(graySampleArea, mTopLayer->getHandle(), grayListener);
|
|
|
|
EXPECT_TRUE(grayListener->wait_event(300ms))
|
|
<< "timed out waiting for luma event to be received";
|
|
EXPECT_NEAR(grayListener->luma(), luma_gray, error_margin);
|
|
EXPECT_TRUE(greenListener->wait_event(300ms))
|
|
<< "timed out waiting for luma event to be received";
|
|
EXPECT_NEAR(greenListener->luma(), luma_green, error_margin);
|
|
|
|
composer->removeRegionSamplingListener(greenListener);
|
|
composer->removeRegionSamplingListener(grayListener);
|
|
}
|
|
|
|
TEST_F(RegionSamplingTest, DISABLED_TestIfInvalidInputParameters) {
|
|
sp<ISurfaceComposer> composer = ComposerService::getComposerService();
|
|
sp<Listener> listener = new Listener();
|
|
const Rect sampleArea{100, 100, 200, 200};
|
|
// Invalid input sampleArea
|
|
EXPECT_EQ(BAD_VALUE,
|
|
composer->addRegionSamplingListener(Rect::INVALID_RECT, mTopLayer->getHandle(),
|
|
listener));
|
|
listener->reset();
|
|
// Invalid input binder
|
|
EXPECT_EQ(NO_ERROR, composer->addRegionSamplingListener(sampleArea, NULL, listener));
|
|
// Invalid input listener
|
|
EXPECT_EQ(BAD_VALUE,
|
|
composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), NULL));
|
|
EXPECT_EQ(BAD_VALUE, composer->removeRegionSamplingListener(NULL));
|
|
// remove the listener
|
|
composer->removeRegionSamplingListener(listener);
|
|
}
|
|
|
|
TEST_F(RegionSamplingTest, DISABLED_TestCallbackAfterRemoveListener) {
|
|
fill_render(rgba_green);
|
|
sp<ISurfaceComposer> composer = ComposerService::getComposerService();
|
|
sp<Listener> listener = new Listener();
|
|
const Rect sampleArea{100, 100, 200, 200};
|
|
composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener);
|
|
fill_render(rgba_green);
|
|
|
|
EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
|
|
EXPECT_NEAR(listener->luma(), luma_green, error_margin);
|
|
|
|
listener->reset();
|
|
composer->removeRegionSamplingListener(listener);
|
|
fill_render(rgba_green);
|
|
EXPECT_FALSE(listener->wait_event(100ms))
|
|
<< "callback should stop after remove the region sampling listener";
|
|
}
|
|
|
|
TEST_F(RegionSamplingTest, DISABLED_CollectsLumaFromMovingLayer) {
|
|
sp<ISurfaceComposer> composer = ComposerService::getComposerService();
|
|
sp<Listener> listener = new Listener();
|
|
Rect sampleArea{100, 100, 200, 200};
|
|
|
|
// Test: listener in (100, 100). See layer before move, no layer after move.
|
|
fill_render(rgba_blue);
|
|
composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener);
|
|
EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
|
|
EXPECT_NEAR(listener->luma(), luma_blue, error_margin);
|
|
listener->reset();
|
|
SurfaceComposerClient::Transaction{}.setPosition(mContentLayer, 600, 600).apply();
|
|
EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
|
|
EXPECT_NEAR(listener->luma(), luma_gray, error_margin);
|
|
composer->removeRegionSamplingListener(listener);
|
|
|
|
// Test: listener offset to (600, 600). No layer before move, see layer after move.
|
|
fill_render(rgba_green);
|
|
sampleArea.offsetTo(600, 600);
|
|
composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener);
|
|
EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
|
|
EXPECT_NEAR(listener->luma(), luma_gray, error_margin);
|
|
listener->reset();
|
|
SurfaceComposerClient::Transaction{}.setPosition(mContentLayer, 600, 600).apply();
|
|
EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
|
|
EXPECT_NEAR(listener->luma(), luma_green, error_margin);
|
|
composer->removeRegionSamplingListener(listener);
|
|
}
|
|
|
|
} // namespace android::test
|