Multithreaded Surface Replayer that replays traces
Change-Id: Id8d17f74e00d4796e1ea266bdaf9e8dd0af6475b
diff --git a/cmds/surfacereplayer/replayer/Android.mk b/cmds/surfacereplayer/replayer/Android.mk
new file mode 100644
index 0000000..28d4481
--- /dev/null
+++ b/cmds/surfacereplayer/replayer/Android.mk
@@ -0,0 +1,71 @@
+# Copyright 2016 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.
+
+LOCAL_TARGET_DIR := $(TARGET_OUT_DATA)/local/tmp
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(call first-makefiles-under, /frameworks/native/cmds/surfacereplayer/proto)
+
+include $(CLEAR_VARS)
+
+LOCAL_CPPFLAGS := -Weverything -Werror
+LOCAL_CPPFLAGS := -Wno-unused-parameter
+LOCAL_CPPFLAGS := -Wno-format
+
+LOCAL_MODULE := libsurfacereplayer
+
+LOCAL_SRC_FILES := \
+ BufferQueueScheduler.cpp \
+ Event.cpp \
+ Replayer.cpp \
+
+LOCAL_SHARED_LIBRARIES := \
+ libEGL \
+ libGLESv2 \
+ libbinder \
+ libcutils \
+ libgui \
+ libui \
+ libutils \
+ libprotobuf-cpp-full \
+
+LOCAL_STATIC_LIBRARIES := \
+ libtrace_proto \
+
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/..
+
+include $(BUILD_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := surfacereplayer
+
+LOCAL_SRC_FILES := \
+ Main.cpp \
+
+LOCAL_SHARED_LIBRARIES := \
+ libprotobuf-cpp-full \
+ libsurfacereplayer \
+ libutils \
+
+LOCAL_STATIC_LIBRARIES := \
+ libtrace_proto \
+
+LOCAL_CPPFLAGS := -Weverything -Werror
+LOCAL_CPPFLAGS := -Wno-unused-parameter
+
+LOCAL_MODULE_PATH := $(LOCAL_TARGET_DIR)
+
+include $(BUILD_EXECUTABLE)
diff --git a/cmds/surfacereplayer/replayer/BufferQueueScheduler.cpp b/cmds/surfacereplayer/replayer/BufferQueueScheduler.cpp
new file mode 100644
index 0000000..cad11a0
--- /dev/null
+++ b/cmds/surfacereplayer/replayer/BufferQueueScheduler.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "BufferQueueScheduler"
+
+#include "BufferQueueScheduler.h"
+
+#include <android/native_window.h>
+#include <gui/Surface.h>
+
+using namespace android;
+
+BufferQueueScheduler::BufferQueueScheduler(
+ const sp<SurfaceControl>& surfaceControl, const RGB& color, int id)
+ : mSurfaceControl(surfaceControl), mColor(color), mSurfaceId(id), mContinueScheduling(true) {}
+
+void BufferQueueScheduler::startScheduling() {
+ ALOGV("Starting Scheduler for %d Layer", mSurfaceId);
+ std::unique_lock<std::mutex> lock(mMutex);
+ if (mSurfaceControl == nullptr) {
+ mCondition.wait(lock, [&] { return (mSurfaceControl != nullptr); });
+ }
+
+ while (mContinueScheduling) {
+ while (true) {
+ if (mBufferEvents.empty()) {
+ break;
+ }
+
+ BufferEvent event = mBufferEvents.front();
+ lock.unlock();
+
+ bufferUpdate(event.dimensions);
+ fillSurface(event.event);
+ lock.lock();
+ mBufferEvents.pop();
+ }
+ mCondition.wait(lock);
+ }
+}
+
+void BufferQueueScheduler::addEvent(const BufferEvent& event) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ mBufferEvents.push(event);
+ mCondition.notify_one();
+}
+
+void BufferQueueScheduler::stopScheduling() {
+ std::lock_guard<std::mutex> lock(mMutex);
+ mContinueScheduling = false;
+ mCondition.notify_one();
+}
+
+void BufferQueueScheduler::setSurfaceControl(
+ const sp<SurfaceControl>& surfaceControl, const RGB& color) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ mSurfaceControl = surfaceControl;
+ mColor = color;
+ mCondition.notify_one();
+}
+
+void BufferQueueScheduler::bufferUpdate(const Dimensions& dimensions) {
+ sp<Surface> s = mSurfaceControl->getSurface();
+ s->setBuffersDimensions(dimensions.width, dimensions.height);
+}
+
+void BufferQueueScheduler::fillSurface(std::shared_ptr<Event> event) {
+ ANativeWindow_Buffer outBuffer;
+ sp<Surface> s = mSurfaceControl->getSurface();
+
+ status_t status = s->lock(&outBuffer, nullptr);
+
+ if (status != NO_ERROR) {
+ ALOGE("fillSurface: failed to lock buffer, (%d)", status);
+ return;
+ }
+
+ auto img = reinterpret_cast<uint8_t*>(outBuffer.bits);
+ for (int y = 0; y < outBuffer.height; y++) {
+ for (int x = 0; x < outBuffer.width; x++) {
+ uint8_t* pixel = img + (4 * (y * outBuffer.stride + x));
+ pixel[0] = mColor.r;
+ pixel[1] = mColor.g;
+ pixel[2] = mColor.b;
+ pixel[3] = LAYER_ALPHA;
+ }
+ }
+
+ event->readyToExecute();
+
+ status = s->unlockAndPost();
+
+ ALOGE_IF(status != NO_ERROR, "fillSurface: failed to unlock and post buffer, (%d)", status);
+}
diff --git a/cmds/surfacereplayer/replayer/BufferQueueScheduler.h b/cmds/surfacereplayer/replayer/BufferQueueScheduler.h
new file mode 100644
index 0000000..fc8e20c
--- /dev/null
+++ b/cmds/surfacereplayer/replayer/BufferQueueScheduler.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016 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_SURFACEREPLAYER_BUFFERQUEUESCHEDULER_H
+#define ANDROID_SURFACEREPLAYER_BUFFERQUEUESCHEDULER_H
+
+#include "Color.h"
+#include "Event.h"
+
+#include <gui/SurfaceControl.h>
+
+#include <utils/StrongPointer.h>
+
+#include <atomic>
+#include <condition_variable>
+#include <mutex>
+#include <queue>
+#include <utility>
+
+namespace android {
+
+auto constexpr LAYER_ALPHA = 190;
+
+struct Dimensions {
+ Dimensions() = default;
+ Dimensions(int w, int h) : width(w), height(h) {}
+
+ int width = 0;
+ int height = 0;
+};
+
+struct BufferEvent {
+ BufferEvent() = default;
+ BufferEvent(std::shared_ptr<Event> e, Dimensions d) : event(e), dimensions(d) {}
+
+ std::shared_ptr<Event> event;
+ Dimensions dimensions;
+};
+
+class BufferQueueScheduler {
+ public:
+ BufferQueueScheduler(const sp<SurfaceControl>& surfaceControl, const RGB& color, int id);
+
+ void startScheduling();
+ void addEvent(const BufferEvent&);
+ void stopScheduling();
+
+ void setSurfaceControl(const sp<SurfaceControl>& surfaceControl, const RGB& color);
+
+ private:
+ void bufferUpdate(const Dimensions& dimensions);
+
+ // Lock and fill the surface, block until the event is signaled by the main loop,
+ // then unlock and post the buffer.
+ void fillSurface(std::shared_ptr<Event>);
+
+ sp<SurfaceControl> mSurfaceControl;
+ RGB mColor;
+ const int mSurfaceId;
+
+ bool mContinueScheduling;
+
+ std::queue<BufferEvent> mBufferEvents;
+ std::mutex mMutex;
+ std::condition_variable mCondition;
+};
+
+} // namespace android
+#endif
diff --git a/cmds/surfacereplayer/replayer/Color.h b/cmds/surfacereplayer/replayer/Color.h
new file mode 100644
index 0000000..d644b8d
--- /dev/null
+++ b/cmds/surfacereplayer/replayer/Color.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2016 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_SURFACEREPLAYER_COLOR_H
+#define ANDROID_SURFACEREPLAYER_COLOR_H
+
+#include <cmath>
+#include <cstdlib>
+
+namespace android {
+
+typedef struct RGB {
+ RGB() = default;
+ RGB(uint8_t rIn, uint8_t gIn, uint8_t bIn) : r(rIn), g(gIn), b(bIn) {}
+
+ uint8_t r = 0;
+ uint8_t g = 0;
+ uint8_t b = 0;
+} RGB;
+
+typedef struct HSV {
+ HSV() = default;
+ HSV(double hIn, double sIn, double vIn) : h(hIn), s(sIn), v(vIn) {}
+
+ double h = 0;
+ double s = 0;
+ double v = 0;
+} HSV;
+
+static inline RGB HSVToRGB(HSV hsv) {
+ using namespace std;
+ double r = 0, g = 0, b = 0;
+
+ if (hsv.s == 0) {
+ r = hsv.v;
+ g = hsv.v;
+ b = hsv.v;
+ } else {
+ hsv.h = static_cast<int>(hsv.h) % 360;
+ hsv.h = hsv.h / 60;
+
+ int i = static_cast<int>(trunc(hsv.h));
+ double f = hsv.h - i;
+
+ double x = hsv.v * (1.0 - hsv.s);
+ double y = hsv.v * (1.0 - (hsv.s * f));
+ double z = hsv.v * (1.0 - (hsv.s * (1.0 - f)));
+
+ switch (i) {
+ case 0:
+ r = hsv.v;
+ g = z;
+ b = x;
+ break;
+
+ case 1:
+ r = y;
+ g = hsv.v;
+ b = x;
+ break;
+
+ case 2:
+ r = x;
+ g = hsv.v;
+ b = z;
+ break;
+
+ case 3:
+ r = x;
+ g = y;
+ b = hsv.v;
+ break;
+
+ case 4:
+ r = z;
+ g = x;
+ b = hsv.v;
+ break;
+
+ default:
+ r = hsv.v;
+ g = x;
+ b = y;
+ break;
+ }
+ }
+
+ return RGB(round(r * 255), round(g * 255), round(b * 255));
+}
+}
+#endif
diff --git a/cmds/surfacereplayer/replayer/Event.cpp b/cmds/surfacereplayer/replayer/Event.cpp
new file mode 100644
index 0000000..390d398
--- /dev/null
+++ b/cmds/surfacereplayer/replayer/Event.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016 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 "Event.h"
+
+using namespace android;
+
+Event::Event(Increment::IncrementCase type) : mIncrementType(type) {}
+
+void Event::readyToExecute() {
+ changeState(Event::EventState::Waiting);
+ waitUntil(Event::EventState::Signaled);
+ changeState(Event::EventState::Running);
+}
+
+void Event::complete() {
+ waitUntil(Event::EventState::Waiting);
+ changeState(Event::EventState::Signaled);
+ waitUntil(Event::EventState::Running);
+}
+
+void Event::waitUntil(Event::EventState state) {
+ std::unique_lock<std::mutex> lock(mLock);
+ mCond.wait(lock, [this, state] { return (mState == state); });
+}
+
+void Event::changeState(Event::EventState state) {
+ std::unique_lock<std::mutex> lock(mLock);
+ mState = state;
+ lock.unlock();
+
+ mCond.notify_one();
+}
+
+Increment::IncrementCase Event::getIncrementType() {
+ return mIncrementType;
+}
diff --git a/cmds/surfacereplayer/replayer/Event.h b/cmds/surfacereplayer/replayer/Event.h
new file mode 100644
index 0000000..44b60f5
--- /dev/null
+++ b/cmds/surfacereplayer/replayer/Event.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016 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_SURFACEREPLAYER_EVENT_H
+#define ANDROID_SURFACEREPLAYER_EVENT_H
+
+#include <frameworks/native/cmds/surfacereplayer/proto/src/trace.pb.h>
+
+#include <condition_variable>
+#include <mutex>
+
+namespace android {
+
+class Event {
+ public:
+ Event(Increment::IncrementCase);
+
+ enum class EventState {
+ SettingUp, // Completing as much time-independent work as possible
+ Waiting, // Waiting for signal from main thread to finish execution
+ Signaled, // Signaled by main thread, about to immediately switch to Running
+ Running // Finishing execution of rest of work
+ };
+
+ void readyToExecute();
+ void complete();
+
+ Increment::IncrementCase getIncrementType();
+
+ private:
+ void waitUntil(EventState state);
+ void changeState(EventState state);
+
+ std::mutex mLock;
+ std::condition_variable mCond;
+
+ EventState mState = EventState::SettingUp;
+
+ Increment::IncrementCase mIncrementType;
+};
+}
+#endif
diff --git a/cmds/surfacereplayer/replayer/Main.cpp b/cmds/surfacereplayer/replayer/Main.cpp
new file mode 100644
index 0000000..bf5da52
--- /dev/null
+++ b/cmds/surfacereplayer/replayer/Main.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2016 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.
+ */
+
+/*
+ * Replayer - Main.cpp
+ *
+ * 1. Get flags from command line
+ * 2. Commit actions or settings based on the flags
+ * 3. Initalize a replayer object with the filename passed in
+ * 4. Replay
+ * 5. Exit successfully or print error statement
+ */
+
+#include <replayer/Replayer.h>
+
+#include <csignal>
+#include <iostream>
+#include <stdlib.h>
+#include <unistd.h>
+
+using namespace android;
+
+void printHelpMenu() {
+ std::cout << "SurfaceReplayer options:\n";
+ std::cout << "Usage: surfacereplayer [OPTIONS...] <TRACE FILE>\n";
+ std::cout << " File path must be absolute" << std::endl << std::endl;
+
+ std::cout << " -m Stops the replayer at the start of the trace and switches ";
+ "to manual replay\n";
+
+ std::cout << " -t [Number of Threads] Specifies the number of threads to be used while "
+ "replaying (default is " << android::DEFAULT_THREADS << ")\n";
+
+ std::cout << " -h Display help menu\n";
+
+ std::cout << std::endl;
+}
+
+int main(int argc, char** argv) {
+ std::string filename;
+ bool pauseBeginning = false;
+ int numThreads = DEFAULT_THREADS;
+
+ int opt = 0;
+ while ((opt = getopt(argc, argv, "pt:h?")) != -1) {
+ switch (opt) {
+ case 'm':
+ pauseBeginning = true;
+ break;
+ case 't':
+ numThreads = atoi(optarg);
+ break;
+ case 'h':
+ case '?':
+ printHelpMenu();
+ exit(0);
+ default:
+ std::cerr << "Invalid argument...exiting" << std::endl;
+ printHelpMenu();
+ exit(0);
+ }
+ }
+
+ char** input = argv + optind;
+ if (input[0] == NULL) {
+ std::cerr << "No trace file provided...exiting" << std::endl;
+ abort();
+ }
+ filename.assign(input[0]);
+
+ android::Replayer r(filename, pauseBeginning, numThreads);
+ auto status = r.replay();
+
+ if (status == NO_ERROR) {
+ std::cout << "Successfully finished replaying trace" << std::endl;
+ } else {
+ std::cerr << "Trace replayer returned error: " << status << std::endl;
+ }
+
+ return 0;
+}
diff --git a/cmds/surfacereplayer/replayer/Replayer.cpp b/cmds/surfacereplayer/replayer/Replayer.cpp
new file mode 100644
index 0000000..950ebbb
--- /dev/null
+++ b/cmds/surfacereplayer/replayer/Replayer.cpp
@@ -0,0 +1,514 @@
+/* Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SurfaceReplayer"
+
+#include "Replayer.h"
+
+#include <android/native_window.h>
+
+#include <binder/IMemory.h>
+
+#include <gui/ISurfaceComposer.h>
+#include <gui/Surface.h>
+#include <private/gui/ComposerService.h>
+#include <private/gui/LayerState.h>
+
+#include <ui/DisplayInfo.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+#include <utils/Trace.h>
+
+#include <chrono>
+#include <cmath>
+#include <condition_variable>
+#include <cstdlib>
+#include <fstream>
+#include <functional>
+#include <iostream>
+#include <mutex>
+#include <string>
+#include <thread>
+
+using namespace android;
+
+std::atomic_bool Replayer::sReplayingManually(false);
+
+Replayer::Replayer(const std::string& filename, bool replayManually, int numThreads)
+ : mTrace(), mLoaded(false), mIncrementIndex(0), mCurrentTime(0), mNumThreads(numThreads) {
+ srand(RAND_COLOR_SEED);
+
+ std::fstream input(filename, std::ios::in | std::ios::binary);
+
+ mLoaded = mTrace.ParseFromIstream(&input);
+ if (!mLoaded) {
+ std::cerr << "Trace did not load. Does " << filename << " exist?" << std::endl;
+ abort();
+ }
+
+ mCurrentTime = mTrace.increment(0).time_stamp();
+
+ sReplayingManually.store(replayManually);
+}
+
+Replayer::Replayer(const Trace& t, bool replayManually, int numThreads)
+ : mTrace(t), mLoaded(true), mIncrementIndex(0), mCurrentTime(0), mNumThreads(numThreads) {
+ srand(RAND_COLOR_SEED);
+ mCurrentTime = mTrace.increment(0).time_stamp();
+
+ sReplayingManually.store(replayManually);
+}
+
+status_t Replayer::replay() {
+ // for manual control
+ signal(SIGINT, Replayer::stopAutoReplayHandler);
+
+ ALOGV("There are %d increments.", mTrace.increment_size());
+
+ status_t status = loadSurfaceComposerClient();
+
+ if (status != NO_ERROR) {
+ ALOGE("Couldn't create SurfaceComposerClient (%d)", status);
+ return status;
+ }
+
+ SurfaceComposerClient::enableVSyncInjections(true);
+
+ initReplay();
+
+ ALOGV("Starting actual Replay!");
+ while (!mPendingIncrements.empty()) {
+ waitForConsoleCommmand();
+
+ auto pastIncrement = mTrace.increment(mIncrementIndex);
+
+ waitUntilTimestamp(pastIncrement.time_stamp());
+
+ auto event = mPendingIncrements.front();
+ mPendingIncrements.pop();
+
+ event->complete();
+
+ if (event->getIncrementType() == Increment::kVsyncEvent) {
+ mWaitingForNextVSync = false;
+ }
+
+ if (mIncrementIndex + mNumThreads < mTrace.increment_size()) {
+ status = dispatchEvent(mIncrementIndex + mNumThreads);
+
+ if (status != NO_ERROR) {
+ SurfaceComposerClient::enableVSyncInjections(false);
+ return status;
+ }
+ }
+
+ mIncrementIndex++;
+ mCurrentTime = pastIncrement.time_stamp();
+ }
+
+ SurfaceComposerClient::enableVSyncInjections(false);
+
+ return status;
+}
+
+status_t Replayer::initReplay() {
+ for (int i = 0; i < mNumThreads && i < mTrace.increment_size(); i++) {
+ status_t status = dispatchEvent(i);
+
+ if (status != NO_ERROR) {
+ ALOGE("Unable to dispatch event (%d)", status);
+ return status;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+void Replayer::stopAutoReplayHandler(int /*signal*/) {
+ if (sReplayingManually) {
+ SurfaceComposerClient::enableVSyncInjections(false);
+ exit(0);
+ }
+
+ sReplayingManually.store(true);
+}
+
+void Replayer::waitForConsoleCommmand() {
+ if (!sReplayingManually || mWaitingForNextVSync) {
+ return;
+ }
+
+ while (true) {
+ std::string input = "";
+ std::cout << "> ";
+ getline(std::cin, input);
+
+ if (input.empty()) {
+ input = mLastInput;
+ }
+
+ input = mLastInput;
+ if (input == "n") { // next vsync
+ mWaitingForNextVSync = true;
+ break;
+ } else if (input == "c") { // continue
+ sReplayingManually.store(false);
+ mWaitingForNextVSync = false;
+ break;
+ } else if (input == "h") { // help
+ // add help menu
+ }
+
+ std::cout << "Invalid Command" << std::endl;
+ }
+}
+
+status_t Replayer::dispatchEvent(int index) {
+ auto increment = mTrace.increment(index);
+ std::shared_ptr<Event> event = std::make_shared<Event>(increment.increment_case());
+ mPendingIncrements.push(event);
+
+ status_t status = NO_ERROR;
+ switch (increment.increment_case()) {
+ case increment.kTransaction: {
+ std::thread(&Replayer::doTransaction, this, increment.transaction(), event).detach();
+ } break;
+ case increment.kCreate: {
+ std::thread(&Replayer::createSurfaceControl, this, increment.create(), event).detach();
+ } break;
+ case increment.kDelete: {
+ std::thread(&Replayer::deleteSurfaceControl, this, increment.delete_(), event).detach();
+ } break;
+ case increment.kBufferUpdate: {
+ std::lock_guard<std::mutex> lock1(mLayerLock);
+ std::lock_guard<std::mutex> lock2(mBufferQueueSchedulerLock);
+
+ Dimensions dimensions(increment.buffer_update().w(), increment.buffer_update().h());
+ BufferEvent bufferEvent(event, dimensions);
+
+ auto layerId = increment.buffer_update().id();
+ if (mBufferQueueSchedulers.count(layerId) == 0) {
+ mBufferQueueSchedulers[layerId] = std::make_shared<BufferQueueScheduler>(
+ mLayers[layerId], mColors[layerId], layerId);
+ mBufferQueueSchedulers[layerId]->addEvent(bufferEvent);
+
+ std::thread(&BufferQueueScheduler::startScheduling,
+ mBufferQueueSchedulers[increment.buffer_update().id()].get())
+ .detach();
+ } else {
+ auto bqs = mBufferQueueSchedulers[increment.buffer_update().id()];
+ bqs->addEvent(bufferEvent);
+ }
+ } break;
+ case increment.kVsyncEvent: {
+ std::thread(&Replayer::injectVSyncEvent, this, increment.vsync_event(), event).detach();
+ } break;
+ default:
+ ALOGE("Unknown Increment Type: %d", increment.increment_case());
+ status = BAD_VALUE;
+ break;
+ }
+
+ return status;
+}
+
+status_t Replayer::doTransaction(const Transaction& t, const std::shared_ptr<Event>& event) {
+ ALOGV("Started Transaction");
+
+ if (t.change_size() == 0) {
+ event->readyToExecute();
+ return NO_ERROR;
+ }
+
+ Change change = t.change(0);
+
+ std::unique_lock<std::mutex> lock(mLayerLock);
+ if (mLayers[change.id()] == nullptr) {
+ mLayerCond.wait(lock, [&] { return (mLayers[change.id()] != nullptr); });
+ }
+ lock.unlock();
+
+ SurfaceComposerClient::openGlobalTransaction();
+
+ status_t status = NO_ERROR;
+
+ for (const Change& change : t.change()) {
+ std::unique_lock<std::mutex> lock(mLayerLock);
+ if (mLayers[change.id()] == nullptr) {
+ mLayerCond.wait(lock, [&] { return (mLayers[change.id()] != nullptr); });
+ }
+
+ switch (change.Change_case()) {
+ case Change::ChangeCase::kPosition:
+ status = setPosition(change.id(), change.position());
+ break;
+ case Change::ChangeCase::kSize:
+ status = setSize(change.id(), change.size());
+ break;
+ case Change::ChangeCase::kAlpha:
+ status = setAlpha(change.id(), change.alpha());
+ break;
+ case Change::ChangeCase::kLayer:
+ status = setLayer(change.id(), change.layer());
+ break;
+ case Change::ChangeCase::kCrop:
+ status = setCrop(change.id(), change.crop());
+ break;
+ case Change::ChangeCase::kMatrix:
+ status = setMatrix(change.id(), change.matrix());
+ break;
+ case Change::ChangeCase::kFinalCrop:
+ status = setFinalCrop(change.id(), change.final_crop());
+ break;
+ case Change::ChangeCase::kOverrideScalingMode:
+ status = setOverrideScalingMode(change.id(), change.override_scaling_mode());
+ break;
+ case Change::ChangeCase::kTransparentRegionHint:
+ status = setTransparentRegionHint(change.id(), change.transparent_region_hint());
+ break;
+ case Change::ChangeCase::kLayerStack:
+ status = setLayerStack(change.id(), change.layer_stack());
+ break;
+ case Change::ChangeCase::kHiddenFlag:
+ status = setHiddenFlag(change.id(), change.hidden_flag());
+ break;
+ case Change::ChangeCase::kOpaqueFlag:
+ status = setOpaqueFlag(change.id(), change.opaque_flag());
+ break;
+ case Change::ChangeCase::kSecureFlag:
+ status = setSecureFlag(change.id(), change.secure_flag());
+ break;
+ case Change::ChangeCase::kDeferredTransaction:
+ waitUntilDeferredTransactionLayerExists(change.deferred_transaction(), lock);
+ status = setDeferredTransaction(change.id(), change.deferred_transaction());
+ break;
+ default:
+ status = NO_ERROR;
+ break;
+ }
+
+ if (status != NO_ERROR) {
+ ALOGE("SET TRANSACTION FAILED");
+ return status;
+ }
+ }
+
+ if (t.animation()) {
+ SurfaceComposerClient::setAnimationTransaction();
+ }
+
+ event->readyToExecute();
+
+ SurfaceComposerClient::closeGlobalTransaction(t.synchronous());
+
+ ALOGV("Ended Transaction");
+
+ return status;
+}
+
+status_t Replayer::setPosition(uint32_t id, const PositionChange& pc) {
+ ALOGV("Layer %d: Setting Position -- x=%f, y=%f", id, pc.x(), pc.y());
+ return mLayers[id]->setPosition(pc.x(), pc.y());
+}
+
+status_t Replayer::setSize(uint32_t id, const SizeChange& sc) {
+ ALOGV("Layer %d: Setting Size -- w=%u, h=%u", id, sc.w(), sc.h());
+ return mLayers[id]->setSize(sc.w(), sc.h());
+}
+
+status_t Replayer::setLayer(uint32_t id, const LayerChange& lc) {
+ ALOGV("Layer %d: Setting Layer -- layer=%d", id, lc.layer());
+ return mLayers[id]->setLayer(lc.layer());
+}
+
+status_t Replayer::setAlpha(uint32_t id, const AlphaChange& ac) {
+ ALOGV("Layer %d: Setting Alpha -- alpha=%f", id, ac.alpha());
+ return mLayers[id]->setAlpha(ac.alpha());
+}
+
+status_t Replayer::setCrop(uint32_t id, const CropChange& cc) {
+ ALOGV("Layer %d: Setting Crop -- left=%d, top=%d, right=%d, bottom=%d", id,
+ cc.rectangle().left(), cc.rectangle().top(), cc.rectangle().right(),
+ cc.rectangle().bottom());
+
+ Rect r = Rect(cc.rectangle().left(), cc.rectangle().top(), cc.rectangle().right(),
+ cc.rectangle().bottom());
+ return mLayers[id]->setCrop(r);
+}
+
+status_t Replayer::setFinalCrop(uint32_t id, const FinalCropChange& fcc) {
+ ALOGV("Layer %d: Setting Final Crop -- left=%d, top=%d, right=%d, bottom=%d", id,
+ fcc.rectangle().left(), fcc.rectangle().top(), fcc.rectangle().right(),
+ fcc.rectangle().bottom());
+ Rect r = Rect(fcc.rectangle().left(), fcc.rectangle().top(), fcc.rectangle().right(),
+ fcc.rectangle().bottom());
+ return mLayers[id]->setFinalCrop(r);
+}
+
+status_t Replayer::setMatrix(uint32_t id, const MatrixChange& mc) {
+ ALOGV("Layer %d: Setting Matrix -- dsdx=%f, dtdx=%f, dsdy=%f, dtdy=%f", id, mc.dsdx(),
+ mc.dtdx(), mc.dsdy(), mc.dtdy());
+ return mLayers[id]->setMatrix(mc.dsdx(), mc.dtdx(), mc.dsdy(), mc.dtdy());
+}
+
+status_t Replayer::setOverrideScalingMode(uint32_t id, const OverrideScalingModeChange& osmc) {
+ ALOGV("Layer %d: Setting Override Scaling Mode -- mode=%d", id, osmc.override_scaling_mode());
+ return mLayers[id]->setOverrideScalingMode(osmc.override_scaling_mode());
+}
+
+status_t Replayer::setTransparentRegionHint(uint32_t id, const TransparentRegionHintChange& trhc) {
+ ALOGV("Setting Transparent Region Hint");
+ Region re = Region();
+
+ for (auto r : trhc.region()) {
+ Rect rect = Rect(r.left(), r.top(), r.right(), r.bottom());
+ re.merge(rect);
+ }
+
+ return mLayers[id]->setTransparentRegionHint(re);
+}
+
+status_t Replayer::setLayerStack(uint32_t id, const LayerStackChange& lsc) {
+ ALOGV("Layer %d: Setting LayerStack -- layer_stack=%d", id, lsc.layer_stack());
+ return mLayers[id]->setLayerStack(lsc.layer_stack());
+}
+
+status_t Replayer::setHiddenFlag(uint32_t id, const HiddenFlagChange& hfc) {
+ ALOGV("Layer %d: Setting Hidden Flag -- hidden_flag=%d", id, hfc.hidden_flag());
+ uint32_t flag = hfc.hidden_flag() ? layer_state_t::eLayerHidden : 0;
+
+ return mLayers[id]->setFlags(flag, layer_state_t::eLayerHidden);
+}
+
+status_t Replayer::setOpaqueFlag(uint32_t id, const OpaqueFlagChange& ofc) {
+ ALOGV("Layer %d: Setting Opaque Flag -- opaque_flag=%d", id, ofc.opaque_flag());
+ uint32_t flag = ofc.opaque_flag() ? layer_state_t::eLayerOpaque : 0;
+
+ return mLayers[id]->setFlags(flag, layer_state_t::eLayerOpaque);
+}
+
+status_t Replayer::setSecureFlag(uint32_t id, const SecureFlagChange& sfc) {
+ ALOGV("Layer %d: Setting Secure Flag -- secure_flag=%d", id, sfc.secure_flag());
+ uint32_t flag = sfc.secure_flag() ? layer_state_t::eLayerSecure : 0;
+
+ return mLayers[id]->setFlags(flag, layer_state_t::eLayerSecure);
+}
+
+status_t Replayer::setDeferredTransaction(uint32_t id, const DeferredTransactionChange& dtc) {
+ ALOGV("Layer %d: Setting Deferred Transaction -- layer_id=%d, "
+ "frame_number=%llu",
+ id, dtc.layer_id(), dtc.frame_number());
+ if (mLayers.count(dtc.layer_id()) == 0 || mLayers[dtc.layer_id()] == nullptr) {
+ ALOGE("Layer %d not found in Deferred Transaction", dtc.layer_id());
+ return BAD_VALUE;
+ }
+
+ auto handle = mLayers[dtc.layer_id()]->getHandle();
+
+ return mLayers[id]->deferTransactionUntil(handle, dtc.frame_number());
+}
+
+status_t Replayer::createSurfaceControl(const Create& create, const std::shared_ptr<Event>& event) {
+ event->readyToExecute();
+
+ ALOGV("Creating Surface Control: ID: %d", create.id());
+ sp<SurfaceControl> surfaceControl = mComposerClient->createSurface(
+ String8(create.name().c_str()), create.w(), create.h(), PIXEL_FORMAT_RGBA_8888, 0);
+
+ if (surfaceControl == nullptr) {
+ ALOGE("CreateSurfaceControl: unable to create surface control");
+ return BAD_VALUE;
+ }
+
+ std::lock_guard<std::mutex> lock1(mLayerLock);
+ auto& layer = mLayers[create.id()];
+ layer = surfaceControl;
+
+ mColors[create.id()] = HSVToRGB(HSV(rand() % 360, 1, 1));
+
+ mLayerCond.notify_all();
+
+ std::lock_guard<std::mutex> lock2(mBufferQueueSchedulerLock);
+ if (mBufferQueueSchedulers.count(create.id()) != 0) {
+ mBufferQueueSchedulers[create.id()]->setSurfaceControl(
+ mLayers[create.id()], mColors[create.id()]);
+ }
+
+ return NO_ERROR;
+}
+
+status_t Replayer::deleteSurfaceControl(
+ const Delete& delete_, const std::shared_ptr<Event>& event) {
+ ALOGV("Deleting %d Surface Control", delete_.id());
+ event->readyToExecute();
+
+ std::lock_guard<std::mutex> lock1(mPendingLayersLock);
+
+ mLayersPendingRemoval.push_back(delete_.id());
+
+ auto iterator = mBufferQueueSchedulers.find(delete_.id());
+ if (iterator != mBufferQueueSchedulers.end()) {
+ (*iterator).second->stopScheduling();
+ }
+
+ std::lock_guard<std::mutex> lock2(mLayerLock);
+ mComposerClient->destroySurface(mLayers[delete_.id()]->getHandle());
+
+ return NO_ERROR;
+}
+
+void Replayer::doDeleteSurfaceControls() {
+ std::lock_guard<std::mutex> lock1(mPendingLayersLock);
+ std::lock_guard<std::mutex> lock2(mLayerLock);
+ if (!mLayersPendingRemoval.empty()) {
+ for (int id : mLayersPendingRemoval) {
+ mLayers.erase(id);
+ mColors.erase(id);
+ mBufferQueueSchedulers.erase(id);
+ }
+ mLayersPendingRemoval.clear();
+ }
+}
+
+status_t Replayer::injectVSyncEvent(
+ const VSyncEvent& vSyncEvent, const std::shared_ptr<Event>& event) {
+ ALOGV("Injecting VSync Event");
+
+ doDeleteSurfaceControls();
+
+ event->readyToExecute();
+
+ SurfaceComposerClient::injectVSync(vSyncEvent.when());
+
+ return NO_ERROR;
+}
+
+void Replayer::waitUntilTimestamp(int64_t timestamp) {
+ ALOGV("Waiting for %lld nanoseconds...", static_cast<int64_t>(timestamp - mCurrentTime));
+ std::this_thread::sleep_for(std::chrono::nanoseconds(timestamp - mCurrentTime));
+}
+
+void Replayer::waitUntilDeferredTransactionLayerExists(
+ const DeferredTransactionChange& dtc, std::unique_lock<std::mutex>& lock) {
+ if (mLayers.count(dtc.layer_id()) == 0 || mLayers[dtc.layer_id()] == nullptr) {
+ mLayerCond.wait(lock, [&] { return (mLayers[dtc.layer_id()] != nullptr); });
+ }
+}
+
+status_t Replayer::loadSurfaceComposerClient() {
+ mComposerClient = new SurfaceComposerClient;
+ return mComposerClient->initCheck();
+}
diff --git a/cmds/surfacereplayer/replayer/Replayer.h b/cmds/surfacereplayer/replayer/Replayer.h
new file mode 100644
index 0000000..ff951d1
--- /dev/null
+++ b/cmds/surfacereplayer/replayer/Replayer.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2016 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_SURFACEREPLAYER_H
+#define ANDROID_SURFACEREPLAYER_H
+
+#include "BufferQueueScheduler.h"
+#include "Color.h"
+#include "Event.h"
+
+#include <frameworks/native/cmds/surfacereplayer/proto/src/trace.pb.h>
+
+#include <gui/SurfaceComposerClient.h>
+#include <gui/SurfaceControl.h>
+
+#include <utils/Errors.h>
+#include <utils/StrongPointer.h>
+
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <queue>
+#include <thread>
+#include <unordered_map>
+#include <utility>
+
+namespace android {
+
+const auto DEFAULT_PATH = "/data/local/tmp/SurfaceTrace.dat";
+const auto RAND_COLOR_SEED = 1000;
+const auto DEFAULT_THREADS = 3;
+
+typedef uint32_t layer_id;
+
+class Replayer {
+ public:
+ Replayer(const std::string& filename, bool replayManually = false,
+ int numThreads = DEFAULT_THREADS);
+ Replayer(const Trace& trace, bool replayManually = false, int numThreads = DEFAULT_THREADS);
+
+ status_t replay();
+
+ private:
+ status_t initReplay();
+
+ void waitForConsoleCommmand();
+ static void stopAutoReplayHandler(int signal);
+
+ status_t dispatchEvent(int index);
+
+ status_t doTransaction(const Transaction& transaction, const std::shared_ptr<Event>& event);
+ status_t createSurfaceControl(const Create& create, const std::shared_ptr<Event>& event);
+ status_t deleteSurfaceControl(const Delete& delete_, const std::shared_ptr<Event>& event);
+ status_t injectVSyncEvent(const VSyncEvent& vsyncEvent, const std::shared_ptr<Event>& event);
+
+ status_t setPosition(uint32_t id, const PositionChange& pc);
+ status_t setSize(uint32_t id, const SizeChange& sc);
+ status_t setAlpha(uint32_t id, const AlphaChange& ac);
+ status_t setLayer(uint32_t id, const LayerChange& lc);
+ status_t setCrop(uint32_t id, const CropChange& cc);
+ status_t setFinalCrop(uint32_t id, const FinalCropChange& fcc);
+ status_t setMatrix(uint32_t id, const MatrixChange& mc);
+ status_t setOverrideScalingMode(uint32_t id, const OverrideScalingModeChange& osmc);
+ status_t setTransparentRegionHint(uint32_t id, const TransparentRegionHintChange& trgc);
+ status_t setLayerStack(uint32_t id, const LayerStackChange& lsc);
+ status_t setHiddenFlag(uint32_t id, const HiddenFlagChange& hfc);
+ status_t setOpaqueFlag(uint32_t id, const OpaqueFlagChange& ofc);
+ status_t setSecureFlag(uint32_t id, const SecureFlagChange& sfc);
+ status_t setDeferredTransaction(uint32_t id, const DeferredTransactionChange& dtc);
+
+ void doDeleteSurfaceControls();
+ void waitUntilTimestamp(int64_t timestamp);
+ void waitUntilDeferredTransactionLayerExists(
+ const DeferredTransactionChange& dtc, std::unique_lock<std::mutex>& lock);
+ status_t loadSurfaceComposerClient();
+
+ Trace mTrace;
+ bool mLoaded = false;
+ int32_t mIncrementIndex = 0;
+ int64_t mCurrentTime = 0;
+ int32_t mNumThreads = DEFAULT_THREADS;
+
+ std::string mLastInput;
+
+ static atomic_bool sReplayingManually;
+ bool mWaitingForNextVSync;
+
+ std::mutex mLayerLock;
+ std::condition_variable mLayerCond;
+ std::unordered_map<layer_id, sp<SurfaceControl>> mLayers;
+ std::unordered_map<layer_id, RGB> mColors;
+
+ std::mutex mPendingLayersLock;
+ std::vector<layer_id> mLayersPendingRemoval;
+
+ std::mutex mBufferQueueSchedulerLock;
+ std::unordered_map<layer_id, std::shared_ptr<BufferQueueScheduler>> mBufferQueueSchedulers;
+
+ sp<SurfaceComposerClient> mComposerClient;
+ std::queue<std::shared_ptr<Event>> mPendingIncrements;
+};
+
+} // namespace android
+#endif