drm_hwcomposer: introduce QueueWorker
Current method to queue work such as display compositions relies on
spinning the CPU until there is space in the queue. This is inefficient
for the time in which the queue happens to fill up.
Introduce a new QueueWorker class to simplify queueing work and handle
blocking for available space more efficiently.
Change-Id: Ida7aa612931700a56ecae3efc7ddd1c86efec699
diff --git a/Android.mk b/Android.mk
index 9efdeae..ee828da 100644
--- a/Android.mk
+++ b/Android.mk
@@ -55,11 +55,9 @@
system/core/libsync/include \
LOCAL_SRC_FILES := \
- autolock.cpp \
drmresources.cpp \
drmcomposition.cpp \
drmcompositor.cpp \
- drmcompositorworker.cpp \
drmconnector.cpp \
drmcrtc.cpp \
drmdisplaycomposition.cpp \
diff --git a/autolock.cpp b/autolock.cpp
deleted file mode 100644
index 1a2ded7..0000000
--- a/autolock.cpp
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-#define LOG_TAG "hwc-drm-auto-lock"
-
-#include "autolock.h"
-
-#include <errno.h>
-#include <pthread.h>
-
-#include <cutils/log.h>
-
-namespace android {
-
-int AutoLock::Lock() {
- if (locked_) {
- ALOGE("Invalid attempt to double lock AutoLock %s", name_);
- return -EINVAL;
- }
- int ret = pthread_mutex_lock(mutex_);
- if (ret) {
- ALOGE("Failed to acquire %s lock %d", name_, ret);
- return ret;
- }
- locked_ = true;
- return 0;
-}
-
-int AutoLock::Unlock() {
- if (!locked_) {
- ALOGE("Invalid attempt to unlock unlocked AutoLock %s", name_);
- return -EINVAL;
- }
- int ret = pthread_mutex_unlock(mutex_);
- if (ret) {
- ALOGE("Failed to release %s lock %d", name_, ret);
- return ret;
- }
- locked_ = false;
- return 0;
-}
-}
diff --git a/autolock.h b/autolock.h
deleted file mode 100644
index 3b824e2..0000000
--- a/autolock.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2015 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 <pthread.h>
-
-namespace android {
-
-class AutoLock {
- public:
- AutoLock(pthread_mutex_t *mutex, const char *const name)
- : mutex_(mutex), name_(name) {
- }
- ~AutoLock() {
- if (locked_)
- Unlock();
- }
-
- AutoLock(const AutoLock &rhs) = delete;
- AutoLock &operator=(const AutoLock &rhs) = delete;
-
- int Lock();
- int Unlock();
-
- private:
- pthread_mutex_t *const mutex_;
- bool locked_ = false;
- const char *const name_;
-};
-}
diff --git a/drmcompositorworker.cpp b/drmcompositorworker.cpp
deleted file mode 100644
index a4e7fc9..0000000
--- a/drmcompositorworker.cpp
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2015 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 "hwc-drm-compositor-worker"
-
-#include "drmdisplaycompositor.h"
-#include "drmcompositorworker.h"
-#include "worker.h"
-
-#include <stdlib.h>
-
-#include <cutils/log.h>
-#include <hardware/hardware.h>
-
-namespace android {
-
-static const int64_t kSquashWait = 500000000LL;
-
-DrmCompositorWorker::DrmCompositorWorker(DrmDisplayCompositor *compositor)
- : Worker("drm-compositor", HAL_PRIORITY_URGENT_DISPLAY),
- compositor_(compositor) {
-}
-
-DrmCompositorWorker::~DrmCompositorWorker() {
-}
-
-int DrmCompositorWorker::Init() {
- return InitWorker();
-}
-
-void DrmCompositorWorker::Routine() {
- int ret;
- if (!compositor_->HaveQueuedComposites()) {
- Lock();
-
- // Only use a timeout if we didn't do a SquashAll last time. This will
- // prevent wait_ret == -ETIMEDOUT which would trigger a SquashAll and be a
- // pointless drain on resources.
- int wait_ret = did_squash_all_ ? WaitForSignalOrExitLocked()
- : WaitForSignalOrExitLocked(kSquashWait);
- Unlock();
-
- switch (wait_ret) {
- case 0:
- break;
- case -EINTR:
- return;
- case -ETIMEDOUT:
- ret = compositor_->SquashAll();
- if (ret)
- ALOGE("Failed to squash all %d", ret);
- did_squash_all_ = true;
- return;
- default:
- ALOGE("Failed to wait for signal, %d", wait_ret);
- return;
- }
- }
-
- ret = compositor_->Composite();
- if (ret)
- ALOGE("Failed to composite! %d", ret);
- did_squash_all_ = false;
-}
-}
diff --git a/drmcompositorworker.h b/drmcompositorworker.h
deleted file mode 100644
index 731bc65..0000000
--- a/drmcompositorworker.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2015 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_DRM_COMPOSITOR_WORKER_H_
-#define ANDROID_DRM_COMPOSITOR_WORKER_H_
-
-#include "worker.h"
-
-namespace android {
-
-class DrmDisplayCompositor;
-
-class DrmCompositorWorker : public Worker {
- public:
- DrmCompositorWorker(DrmDisplayCompositor *compositor);
- ~DrmCompositorWorker() override;
-
- int Init();
-
- protected:
- void Routine() override;
-
- DrmDisplayCompositor *compositor_;
- bool did_squash_all_ = false;
-};
-}
-
-#endif
diff --git a/drmdisplaycompositor.cpp b/drmdisplaycompositor.cpp
index 4581975..3d27f13 100644
--- a/drmdisplaycompositor.cpp
+++ b/drmdisplaycompositor.cpp
@@ -19,10 +19,13 @@
#include "drmdisplaycompositor.h"
-#include <pthread.h>
#include <sched.h>
#include <stdlib.h>
#include <time.h>
+#include <algorithm>
+#include <bitset>
+#include <cinttypes>
+#include <mutex>
#include <sstream>
#include <vector>
@@ -31,7 +34,6 @@
#include <sync/sync.h>
#include <utils/Trace.h>
-#include "autolock.h"
#include "drmcrtc.h"
#include "drmplane.h"
#include "drmresources.h"
@@ -41,6 +43,8 @@
namespace android {
+static const int64_t kSquashWait = 500LL;
+
void SquashState::Init(DrmHwcLayer *layers, size_t num_layers) {
generation_number_++;
valid_history_ = 0;
@@ -177,75 +181,53 @@
}
DrmDisplayCompositor::FrameWorker::FrameWorker(DrmDisplayCompositor *compositor)
- : Worker("frame-worker", HAL_PRIORITY_URGENT_DISPLAY),
+ : QueueWorker("frame-worker", HAL_PRIORITY_URGENT_DISPLAY),
compositor_(compositor) {
}
-DrmDisplayCompositor::FrameWorker::~FrameWorker() {
-}
-
int DrmDisplayCompositor::FrameWorker::Init() {
+ set_max_queue_size(DRM_DISPLAY_COMPOSITOR_MAX_QUEUE_DEPTH);
return InitWorker();
}
void DrmDisplayCompositor::FrameWorker::QueueFrame(
std::unique_ptr<DrmDisplayComposition> composition, int status) {
- Lock();
+ std::unique_ptr<FrameState> frame(
+ new FrameState(std::move(composition), status));
- // Block queue if it gets too large. Otherwise composition will
- // start stacking up and eat limited resources (file descriptors)
- // allocated for these.
- while (frame_queue_.size() >= DRM_DISPLAY_COMPOSITOR_MAX_QUEUE_DEPTH) {
- Unlock();
- sched_yield();
- Lock();
+ auto start = std::chrono::high_resolution_clock::now();
+ int ret = QueueWork(std::move(frame));
+ if (ret) {
+ ALOGE("Unable to queue frame work (%d)", ret);
+ // TODO: error handling (timeout or exit)
+ return;
}
+ auto end = std::chrono::high_resolution_clock::now();
- FrameState frame;
- frame.composition = std::move(composition);
- frame.status = status;
- frame_queue_.push(std::move(frame));
- Unlock();
- Signal();
+ uint64_t duration_us =
+ std::chrono::duration_cast<std::chrono::microseconds>(end - start)
+ .count();
+ if (duration_us > max_duration_us)
+ max_duration_us = duration_us;
}
-void DrmDisplayCompositor::FrameWorker::Routine() {
- int wait_ret = 0;
-
- Lock();
- if (frame_queue_.empty()) {
- wait_ret = WaitForSignalOrExitLocked();
- }
-
- FrameState frame;
- if (!frame_queue_.empty()) {
- frame = std::move(frame_queue_.front());
- frame_queue_.pop();
- }
- Unlock();
-
- if (wait_ret == -EINTR) {
- return;
- } else if (wait_ret) {
- ALOGE("Failed to wait for signal, %d", wait_ret);
- return;
- }
-
- compositor_->ApplyFrame(std::move(frame.composition), frame.status);
+void DrmDisplayCompositor::FrameWorker::ProcessWork(
+ std::unique_ptr<FrameState> frame) {
+ compositor_->ApplyFrame(std::move(frame->composition), frame->status);
}
DrmDisplayCompositor::DrmDisplayCompositor()
- : drm_(NULL),
+ : QueueWorker("drm-compositor", HAL_PRIORITY_URGENT_DISPLAY),
+ drm_(NULL),
display_(-1),
- worker_(this),
frame_worker_(this),
- initialized_(false),
active_(false),
use_hw_overlays_(true),
framebuffer_index_(0),
squash_framebuffer_index_(0),
dump_frames_composited_(0),
- dump_last_timestamp_ns_(0) {
+ dump_last_timestamp_ns_(0),
+ max_duration_us(0) {
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts))
return;
@@ -253,58 +235,32 @@
}
DrmDisplayCompositor::~DrmDisplayCompositor() {
- if (!initialized_)
+ if (!initialized())
return;
- worker_.Exit();
frame_worker_.Exit();
+ Exit();
- int ret = pthread_mutex_lock(&lock_);
- if (ret)
- ALOGE("Failed to acquire compositor lock %d", ret);
+ std::lock_guard<std::mutex> lk(mutex_);
if (mode_.blob_id)
drm_->DestroyPropertyBlob(mode_.blob_id);
if (mode_.old_blob_id)
drm_->DestroyPropertyBlob(mode_.old_blob_id);
- while (!composite_queue_.empty()) {
- composite_queue_.front().reset();
- composite_queue_.pop();
- }
active_composition_.reset();
-
- ret = pthread_mutex_unlock(&lock_);
- if (ret)
- ALOGE("Failed to acquire compositor lock %d", ret);
-
- pthread_mutex_destroy(&lock_);
}
int DrmDisplayCompositor::Init(DrmResources *drm, int display) {
drm_ = drm;
display_ = display;
- int ret = pthread_mutex_init(&lock_, NULL);
- if (ret) {
- ALOGE("Failed to initialize drm compositor lock %d\n", ret);
- return ret;
- }
- ret = worker_.Init();
- if (ret) {
- pthread_mutex_destroy(&lock_);
- ALOGE("Failed to initialize compositor worker %d\n", ret);
- return ret;
- }
- ret = frame_worker_.Init();
- if (ret) {
- pthread_mutex_destroy(&lock_);
- ALOGE("Failed to initialize frame worker %d\n", ret);
- return ret;
- }
+ frame_worker_.Init();
- initialized_ = true;
- return 0;
+ set_max_queue_size(DRM_DISPLAY_COMPOSITOR_MAX_QUEUE_DEPTH);
+ set_idle_timeout(kSquashWait);
+
+ return InitWorker();
}
std::unique_ptr<DrmDisplayComposition> DrmDisplayCompositor::CreateComposition()
@@ -335,29 +291,23 @@
return -ENOENT;
}
- int ret = pthread_mutex_lock(&lock_);
+ auto start = std::chrono::high_resolution_clock::now();
+
+ int ret = QueueWork(std::move(composition));
if (ret) {
- ALOGE("Failed to acquire compositor lock %d", ret);
+ ALOGE("Unable to queue work (%d)", ret);
+ // TODO: error handling (timeout or exit)
return ret;
}
- // Block the queue if it gets too large. Otherwise, SurfaceFlinger will start
- // to eat our buffer handles when we get about 1 second behind.
- while (composite_queue_.size() >= DRM_DISPLAY_COMPOSITOR_MAX_QUEUE_DEPTH) {
- pthread_mutex_unlock(&lock_);
- sched_yield();
- pthread_mutex_lock(&lock_);
- }
+ auto end = std::chrono::high_resolution_clock::now();
- composite_queue_.push(std::move(composition));
+ uint64_t duration_us =
+ std::chrono::duration_cast<std::chrono::microseconds>(end - start)
+ .count();
+ if (duration_us > max_duration_us)
+ max_duration_us = duration_us;
- ret = pthread_mutex_unlock(&lock_);
- if (ret) {
- ALOGE("Failed to release compositor lock %d", ret);
- return ret;
- }
-
- worker_.Signal();
return 0;
}
@@ -866,11 +816,7 @@
}
void DrmDisplayCompositor::ClearDisplay() {
- AutoLock lock(&lock_, "compositor");
- int ret = lock.Lock();
- if (ret)
- return;
-
+ std::lock_guard<std::mutex> lk(mutex_);
if (!active_composition_)
return;
@@ -901,19 +847,12 @@
if (active_composition_)
active_composition_->SignalCompositionDone();
- ret = pthread_mutex_lock(&lock_);
- if (ret)
- ALOGE("Failed to acquire lock for active_composition swap");
-
+ std::lock_guard<std::mutex> lk(mutex_);
active_composition_.swap(composition);
-
- if (!ret)
- ret = pthread_mutex_unlock(&lock_);
- if (ret)
- ALOGE("Failed to release lock for active_composition swap");
}
-int DrmDisplayCompositor::Composite() {
+void DrmDisplayCompositor::ProcessWork(
+ std::unique_ptr<DrmDisplayComposition> composition) {
ATRACE_CALL();
if (!pre_compositor_) {
@@ -921,39 +860,17 @@
int ret = pre_compositor_->Init();
if (ret) {
ALOGE("Failed to initialize OpenGL compositor %d", ret);
- return ret;
+ return;
}
}
- int ret = pthread_mutex_lock(&lock_);
- if (ret) {
- ALOGE("Failed to acquire compositor lock %d", ret);
- return ret;
- }
- if (composite_queue_.empty()) {
- ret = pthread_mutex_unlock(&lock_);
- if (ret)
- ALOGE("Failed to release compositor lock %d", ret);
- return ret;
- }
-
- std::unique_ptr<DrmDisplayComposition> composition(
- std::move(composite_queue_.front()));
-
- composite_queue_.pop();
-
- ret = pthread_mutex_unlock(&lock_);
- if (ret) {
- ALOGE("Failed to release compositor lock %d", ret);
- return ret;
- }
-
+ int ret;
switch (composition->type()) {
case DRM_COMPOSITION_TYPE_FRAME:
ret = PrepareFrame(composition.get());
if (ret) {
ALOGE("Failed to prepare frame for display %d", display_);
- return ret;
+ return;
}
if (composition->geometry_changed()) {
// Send the composition to the kernel to ensure we can commit it. This
@@ -979,7 +896,7 @@
// to signal the release fences from that composition to avoid
// hanging.
ClearDisplay();
- return ret;
+ return;
}
}
frame_worker_.QueueFrame(std::move(composition), ret);
@@ -988,7 +905,7 @@
ret = ApplyDpms(composition.get());
if (ret)
ALOGE("Failed to apply dpms for display %d", display_);
- return ret;
+ break;
case DRM_COMPOSITION_TYPE_MODESET:
mode_.mode = composition->display_mode();
if (mode_.blob_id)
@@ -996,41 +913,19 @@
std::tie(ret, mode_.blob_id) = CreateModeBlob(mode_.mode);
if (ret) {
ALOGE("Failed to create mode blob for display %d", display_);
- return ret;
+ return;
}
mode_.needs_modeset = true;
- return 0;
+ break;
default:
ALOGE("Unknown composition type %d", composition->type());
- return -EINVAL;
+ break;
}
-
- return ret;
-}
-
-bool DrmDisplayCompositor::HaveQueuedComposites() const {
- int ret = pthread_mutex_lock(&lock_);
- if (ret) {
- ALOGE("Failed to acquire compositor lock %d", ret);
- return false;
- }
-
- bool empty_ret = !composite_queue_.empty();
-
- ret = pthread_mutex_unlock(&lock_);
- if (ret) {
- ALOGE("Failed to release compositor lock %d", ret);
- return false;
- }
-
- return empty_ret;
}
int DrmDisplayCompositor::SquashAll() {
- AutoLock lock(&lock_, "compositor");
- int ret = lock.Lock();
- if (ret)
- return ret;
+ std::unique_lock<std::mutex> lk(mutex_);
+ int ret;
if (!active_composition_)
return 0;
@@ -1039,7 +934,7 @@
ret = SquashFrame(active_composition_.get(), comp.get());
// ApplyFrame needs the lock
- lock.Unlock();
+ lk.unlock();
if (!ret)
ApplyFrame(std::move(comp), 0);
@@ -1171,17 +1066,13 @@
}
void DrmDisplayCompositor::Dump(std::ostringstream *out) const {
- int ret = pthread_mutex_lock(&lock_);
- if (ret)
- return;
-
+ std::lock_guard<std::mutex> lk(mutex_);
uint64_t num_frames = dump_frames_composited_;
dump_frames_composited_ = 0;
struct timespec ts;
- ret = clock_gettime(CLOCK_MONOTONIC, &ts);
+ int ret = clock_gettime(CLOCK_MONOTONIC, &ts);
if (ret) {
- pthread_mutex_unlock(&lock_);
return;
}
@@ -1195,11 +1086,21 @@
dump_last_timestamp_ns_ = cur_ts;
+ *out << "----Jank Stats: "
+ << " compositor_max_q_wait_us=" << max_duration_us
+ << " frameworker_max_q_wait_us=" << frame_worker_.max_duration_us
+ << "\n";
+
+ max_duration_us = 0;
+ frame_worker_.max_duration_us = 0;
+
if (active_composition_)
active_composition_->Dump(out);
squash_state_.Dump(out);
+}
- pthread_mutex_unlock(&lock_);
+void DrmDisplayCompositor::ProcessIdle() {
+ SquashAll();
}
}
diff --git a/drmdisplaycompositor.h b/drmdisplaycompositor.h
index 9487cac..961fe72 100644
--- a/drmdisplaycompositor.h
+++ b/drmdisplaycompositor.h
@@ -17,13 +17,13 @@
#ifndef ANDROID_DRM_DISPLAY_COMPOSITOR_H_
#define ANDROID_DRM_DISPLAY_COMPOSITOR_H_
-#include "drmhwcomposer.h"
#include "drmcomposition.h"
-#include "drmcompositorworker.h"
#include "drmframebuffer.h"
+#include "drmhwcomposer.h"
+#include "queue_worker.h"
#include "separate_rects.h"
-#include <pthread.h>
+#include <chrono>
#include <memory>
#include <queue>
#include <sstream>
@@ -81,7 +81,7 @@
std::vector<Region> regions_;
};
-class DrmDisplayCompositor {
+class DrmDisplayCompositor : public QueueWorker<DrmDisplayComposition> {
public:
DrmDisplayCompositor();
~DrmDisplayCompositor();
@@ -90,39 +90,42 @@
std::unique_ptr<DrmDisplayComposition> CreateComposition() const;
int QueueComposition(std::unique_ptr<DrmDisplayComposition> composition);
- int Composite();
+ void ProcessWork(std::unique_ptr<DrmDisplayComposition> composition);
+ void ProcessIdle();
int SquashAll();
void Dump(std::ostringstream *out) const;
std::tuple<uint32_t, uint32_t, int> GetActiveModeResolution();
- bool HaveQueuedComposites() const;
-
SquashState *squash_state() {
return &squash_state_;
}
private:
struct FrameState {
+ FrameState(std::unique_ptr<DrmDisplayComposition> composition, int status)
+ : composition(std::move(composition)), status(status) {
+ }
+
std::unique_ptr<DrmDisplayComposition> composition;
int status = 0;
};
- class FrameWorker : public Worker {
+ class FrameWorker : public QueueWorker<FrameState> {
public:
FrameWorker(DrmDisplayCompositor *compositor);
- ~FrameWorker() override;
int Init();
void QueueFrame(std::unique_ptr<DrmDisplayComposition> composition,
int status);
+ mutable uint64_t max_duration_us;
+
protected:
- void Routine() override;
+ void ProcessWork(std::unique_ptr<FrameState> frame);
private:
DrmDisplayCompositor *compositor_;
- std::queue<FrameState> frame_queue_;
};
struct ModeState {
@@ -158,13 +161,10 @@
DrmResources *drm_;
int display_;
- DrmCompositorWorker worker_;
FrameWorker frame_worker_;
- std::queue<std::unique_ptr<DrmDisplayComposition>> composite_queue_;
std::unique_ptr<DrmDisplayComposition> active_composition_;
- bool initialized_;
bool active_;
bool use_hw_overlays_;
@@ -179,12 +179,13 @@
DrmFramebuffer squash_framebuffers_[2];
// mutable since we need to acquire in HaveQueuedComposites
- mutable pthread_mutex_t lock_;
+ mutable std::mutex mutex_;
// State tracking progress since our last Dump(). These are mutable since
// we need to reset them on every Dump() call.
mutable uint64_t dump_frames_composited_;
mutable uint64_t dump_last_timestamp_ns_;
+ mutable uint64_t max_duration_us;
};
}
diff --git a/drmeventlistener.cpp b/drmeventlistener.cpp
index ca9d7fc..a07bd89 100644
--- a/drmeventlistener.cpp
+++ b/drmeventlistener.cpp
@@ -21,10 +21,10 @@
#include "drmeventlistener.h"
#include "drmresources.h"
-#include <assert.h>
#include <linux/netlink.h>
#include <sys/socket.h>
+#include <assert.h>
#include <cutils/log.h>
#include <xf86drm.h>
diff --git a/hwcomposer.cpp b/hwcomposer.cpp
index 8f08f13..8d8e1d0 100644
--- a/hwcomposer.cpp
+++ b/hwcomposer.cpp
@@ -33,7 +33,6 @@
#include <errno.h>
#include <fcntl.h>
-#include <pthread.h>
#include <sys/param.h>
#include <sys/resource.h>
#include <xf86drm.h>
diff --git a/queue_worker.h b/queue_worker.h
new file mode 100644
index 0000000..7e96eec
--- /dev/null
+++ b/queue_worker.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 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_QUEUE_WORKER_H_
+#define ANDROID_QUEUE_WORKER_H_
+
+#include "worker.h"
+
+#include <queue>
+
+namespace android {
+
+template <typename T>
+class QueueWorker : public Worker {
+ public:
+ static const size_t kDefaultMaxQueueSize = 2;
+ static const int64_t kTimeoutDisabled = -1;
+
+ QueueWorker(const char *name, int priority)
+ : Worker(name, priority),
+ max_queue_size_(kDefaultMaxQueueSize),
+ queue_timeout_ms_(kTimeoutDisabled),
+ idle_timeout_ms_(kTimeoutDisabled),
+ idled_out_(false) {
+ }
+
+ int QueueWork(std::unique_ptr<T> workitem);
+
+ bool IsWorkPending() const {
+ return !queue_.empty();
+ }
+ bool idle() const {
+ return idled_out_;
+ }
+
+ int64_t idle_timeout() {
+ return idle_timeout_ms_;
+ }
+ void set_idle_timeout(int64_t timeout_ms) {
+ idle_timeout_ms_ = timeout_ms;
+ }
+
+ int64_t queue_timeout() {
+ return queue_timeout_ms_;
+ }
+ void set_queue_timeout(int64_t timeout_ms) {
+ queue_timeout_ms_ = timeout_ms;
+ }
+
+ size_t max_queue_size() const {
+ return max_queue_size_;
+ }
+ void set_max_queue_size(size_t size) {
+ max_queue_size_ = size;
+ }
+
+ protected:
+ virtual void ProcessWork(std::unique_ptr<T> workitem) = 0;
+ virtual void ProcessIdle(){}
+ virtual void Routine();
+
+ template <typename Predicate>
+ int WaitCond(std::unique_lock<std::mutex> &lock, Predicate pred,
+ int64_t max_msecs);
+
+ private:
+ std::queue<std::unique_ptr<T>> queue_;
+ size_t max_queue_size_;
+ int64_t queue_timeout_ms_;
+ int64_t idle_timeout_ms_;
+ bool idled_out_;
+};
+
+template <typename T>
+template <typename Predicate>
+int QueueWorker<T>::WaitCond(std::unique_lock<std::mutex> &lock, Predicate pred,
+ int64_t max_msecs) {
+ bool ret = true;
+ auto wait_func = [&] { return pred() || should_exit(); };
+
+ if (max_msecs < 0) {
+ cond_.wait(lock, wait_func);
+ } else {
+ auto timeout = std::chrono::milliseconds(max_msecs);
+ ret = cond_.wait_for(lock, timeout, wait_func);
+ }
+
+ if (!ret)
+ return -ETIMEDOUT;
+ else if (should_exit())
+ return -EINTR;
+
+ return 0;
+}
+
+template <typename T>
+void QueueWorker<T>::Routine() {
+ std::unique_lock<std::mutex> lk(mutex_);
+ std::unique_ptr<T> workitem;
+
+ auto wait_func = [&] { return !queue_.empty(); };
+ int ret =
+ WaitCond(lk, wait_func, idled_out_ ? kTimeoutDisabled : idle_timeout_ms_);
+ switch (ret) {
+ case 0:
+ break;
+ case -ETIMEDOUT:
+ ProcessIdle();
+ idled_out_ = true;
+ return;
+ case -EINTR:
+ default:
+ return;
+ }
+
+ if (!queue_.empty()) {
+ workitem = std::move(queue_.front());
+ queue_.pop();
+ }
+ lk.unlock();
+ cond_.notify_all();
+
+ idled_out_ = false;
+ ProcessWork(std::move(workitem));
+}
+
+template <typename T>
+int QueueWorker<T>::QueueWork(std::unique_ptr<T> workitem) {
+ std::unique_lock<std::mutex> lk(mutex_);
+
+ auto wait_func = [&] { return queue_.size() < max_queue_size_; };
+ int ret = WaitCond(lk, wait_func, queue_timeout_ms_);
+ if (ret)
+ return ret;
+
+ queue_.push(std::move(workitem));
+ lk.unlock();
+
+ cond_.notify_one();
+
+ return 0;
+}
+};
+#endif
diff --git a/tests/Android.mk b/tests/Android.mk
index 5bbda93..b86cca6 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -3,6 +3,7 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
+ queue_worker_test.cpp \
worker_test.cpp
LOCAL_MODULE := hwc-drm-tests
diff --git a/tests/queue_worker_test.cpp b/tests/queue_worker_test.cpp
new file mode 100644
index 0000000..d1c0470
--- /dev/null
+++ b/tests/queue_worker_test.cpp
@@ -0,0 +1,201 @@
+#include <gtest/gtest.h>
+#include <hardware/hardware.h>
+
+#include <chrono>
+#include <mutex>
+
+#include "queue_worker.h"
+
+using android::QueueWorker;
+
+#define UNUSED_ARG(x) (void)(x)
+
+struct TestData {
+ TestData(int val) : value(val) {
+ }
+ virtual ~TestData() {
+ }
+
+ virtual void CheckValue(int prev_value) {
+ ASSERT_EQ(prev_value + 1, value);
+ }
+
+ int value;
+};
+
+struct TestQueueWorker : public QueueWorker<TestData> {
+ TestQueueWorker()
+ : QueueWorker("test-queueworker", HAL_PRIORITY_URGENT_DISPLAY), value(0) {
+ }
+
+ int Init() {
+ return InitWorker();
+ }
+
+ void ProcessWork(std::unique_ptr<TestData> data) {
+ std::lock_guard<std::mutex> blk(block);
+ data->CheckValue(value);
+ {
+ std::lock_guard<std::mutex> lk(lock);
+ value = data->value;
+ }
+ cond.notify_one();
+ }
+
+ void ProcessIdle() {
+ ASSERT_FALSE(idle());
+ }
+
+ std::mutex lock;
+ std::mutex block;
+ std::condition_variable cond;
+ int value;
+};
+
+struct QueueWorkerTest : public testing::Test {
+ static const int kTimeoutMs = 1000;
+ TestQueueWorker qw;
+
+ virtual void SetUp() {
+ qw.Init();
+ }
+ bool QueueValue(int val) {
+ std::unique_ptr<TestData> data(new TestData(val));
+ return !qw.QueueWork(std::move(data));
+ }
+
+ bool WaitFor(int val, int timeout_ms = kTimeoutMs) {
+ std::unique_lock<std::mutex> lk(qw.lock);
+
+ auto timeout = std::chrono::milliseconds(timeout_ms);
+ return qw.cond.wait_for(lk, timeout, [&] { return qw.value == val; });
+ }
+};
+
+struct IdleQueueWorkerTest : public QueueWorkerTest {
+ const int64_t kIdleTimeoutMs = 100;
+
+ virtual void SetUp() {
+ qw.set_idle_timeout(kIdleTimeoutMs);
+ qw.Init();
+ }
+};
+
+TEST_F(QueueWorkerTest, single_queue) {
+ // already isInitialized so should fail
+ ASSERT_NE(qw.Init(), 0);
+
+ ASSERT_EQ(qw.value, 0);
+ ASSERT_TRUE(QueueValue(1));
+ ASSERT_TRUE(WaitFor(1));
+ ASSERT_EQ(qw.value, 1);
+ ASSERT_FALSE(qw.IsWorkPending());
+}
+
+TEST_F(QueueWorkerTest, multiple_waits) {
+ for (int i = 1; i <= 100; i++) {
+ ASSERT_TRUE(QueueValue(i));
+ ASSERT_TRUE(WaitFor(i));
+ ASSERT_EQ(qw.value, i);
+ ASSERT_FALSE(qw.IsWorkPending());
+ }
+}
+
+TEST_F(QueueWorkerTest, multiple_queue) {
+ for (int i = 1; i <= 100; i++) {
+ ASSERT_TRUE(QueueValue(i));
+ }
+ ASSERT_TRUE(WaitFor(100));
+ ASSERT_EQ(qw.value, 100);
+ ASSERT_FALSE(qw.IsWorkPending());
+}
+
+TEST_F(QueueWorkerTest, blocking) {
+ // First wait for inital value to be setup
+ ASSERT_TRUE(QueueValue(1));
+ ASSERT_TRUE(WaitFor(1));
+
+ // Block processing and fill up the queue
+ std::unique_lock<std::mutex> lk(qw.block);
+ size_t expected_value = qw.max_queue_size() + 2;
+ for (size_t i = 2; i <= expected_value; i++) {
+ ASSERT_TRUE(QueueValue(i));
+ }
+
+ qw.set_queue_timeout(100);
+ // any additional queueing should fail
+ ASSERT_FALSE(QueueValue(expected_value + 1));
+
+ // make sure value is not changed while blocked
+ {
+ std::unique_lock<std::mutex> lock(qw.lock);
+ auto timeout = std::chrono::milliseconds(100);
+ ASSERT_FALSE(
+ qw.cond.wait_for(lock, timeout, [&] { return qw.value != 1; }));
+ }
+ ASSERT_EQ(qw.value, 1);
+ ASSERT_TRUE(qw.IsWorkPending());
+
+ // unblock and wait for value to be reached
+ lk.unlock();
+ ASSERT_TRUE(WaitFor(expected_value));
+ ASSERT_FALSE(qw.IsWorkPending());
+}
+
+TEST_F(QueueWorkerTest, exit_slow) {
+ struct SlowData : public TestData {
+ SlowData(int val) : TestData(val) {
+ }
+ void CheckValue(int prev_value) {
+ UNUSED_ARG(prev_value);
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ }
+ };
+ std::unique_ptr<SlowData> data(new SlowData(1));
+ ASSERT_EQ(qw.QueueWork(std::move(data)), 0);
+ data = std::unique_ptr<SlowData>(new SlowData(2));
+ ASSERT_EQ(qw.QueueWork(std::move(data)), 0);
+ qw.Exit();
+ ASSERT_FALSE(qw.initialized());
+}
+
+TEST_F(QueueWorkerTest, exit_empty) {
+ qw.Exit();
+ ASSERT_FALSE(qw.initialized());
+}
+
+TEST_F(QueueWorkerTest, queue_worker_noidling) {
+ ASSERT_TRUE(QueueValue(1));
+ ASSERT_TRUE(WaitFor(1));
+
+ ASSERT_FALSE(qw.idle());
+ auto timeout = std::chrono::milliseconds(200);
+ std::this_thread::sleep_for(timeout);
+ ASSERT_FALSE(qw.idle());
+}
+
+TEST_F(IdleQueueWorkerTest, queue_worker_idling) {
+ ASSERT_TRUE(QueueValue(1));
+ ASSERT_TRUE(WaitFor(1));
+ ASSERT_FALSE(qw.idle());
+
+ auto timeout = std::chrono::milliseconds(kIdleTimeoutMs + 10);
+ std::this_thread::sleep_for(timeout);
+ ASSERT_TRUE(qw.idle());
+ ASSERT_TRUE(QueueValue(2));
+ ASSERT_TRUE(WaitFor(2));
+ ASSERT_FALSE(qw.idle());
+
+ std::this_thread::sleep_for(3 * timeout);
+ ASSERT_TRUE(qw.idle());
+
+ ASSERT_TRUE(QueueValue(3));
+ ASSERT_TRUE(WaitFor(3));
+ for (int i = 4; i <= 100; i++) {
+ QueueValue(i);
+ }
+ ASSERT_FALSE(qw.idle());
+ qw.Exit();
+ ASSERT_FALSE(qw.initialized());
+}
\ No newline at end of file
diff --git a/virtualcompositorworker.cpp b/virtualcompositorworker.cpp
index 639dc86..c1a6d2f 100644
--- a/virtualcompositorworker.cpp
+++ b/virtualcompositorworker.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2015-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.
@@ -17,15 +17,8 @@
#define LOG_TAG "hwc-virtual-compositor-worker"
#include "virtualcompositorworker.h"
-#include "worker.h"
-
-#include <errno.h>
-#include <stdlib.h>
#include <cutils/log.h>
-#include <hardware/hardware.h>
-#include <hardware/hwcomposer.h>
-#include <sched.h>
#include <sw_sync.h>
#include <sync/sync.h>
@@ -35,7 +28,7 @@
static const int kAcquireWaitTimeoutMs = 3000;
VirtualCompositorWorker::VirtualCompositorWorker()
- : Worker("virtual-compositor", HAL_PRIORITY_URGENT_DISPLAY),
+ : QueueWorker("virtual-compositor", HAL_PRIORITY_URGENT_DISPLAY),
timeline_fd_(-1),
timeline_(0),
timeline_current_(0) {
@@ -56,6 +49,8 @@
return ret;
}
timeline_fd_ = ret;
+
+ set_max_queue_size(kMaxQueueDepth);
return InitWorker();
}
@@ -81,41 +76,7 @@
composition->release_timeline = timeline_;
- Lock();
- while (composite_queue_.size() >= kMaxQueueDepth) {
- Unlock();
- sched_yield();
- Lock();
- }
-
- composite_queue_.push(std::move(composition));
- Unlock();
- Signal();
-}
-
-void VirtualCompositorWorker::Routine() {
- int wait_ret = 0;
-
- Lock();
- if (composite_queue_.empty()) {
- wait_ret = WaitForSignalOrExitLocked();
- }
-
- std::unique_ptr<VirtualComposition> composition;
- if (!composite_queue_.empty()) {
- composition = std::move(composite_queue_.front());
- composite_queue_.pop();
- }
- Unlock();
-
- if (wait_ret == -EINTR) {
- return;
- } else if (wait_ret) {
- ALOGE("Failed to wait for signal, %d", wait_ret);
- return;
- }
-
- Compose(std::move(composition));
+ QueueWork(std::move(composition));
}
int VirtualCompositorWorker::CreateNextTimelineFence() {
@@ -135,7 +96,7 @@
return ret;
}
-void VirtualCompositorWorker::Compose(
+void VirtualCompositorWorker::ProcessWork(
std::unique_ptr<VirtualComposition> composition) {
if (!composition.get())
return;
diff --git a/virtualcompositorworker.h b/virtualcompositorworker.h
index 1fc5e43..885cf31 100644
--- a/virtualcompositorworker.h
+++ b/virtualcompositorworker.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2015-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.
@@ -18,13 +18,17 @@
#define ANDROID_VIRTUAL_COMPOSITOR_WORKER_H_
#include "drmhwcomposer.h"
-#include "worker.h"
-
-#include <queue>
+#include "queue_worker.h"
namespace android {
-class VirtualCompositorWorker : public Worker {
+struct VirtualComposition {
+ UniqueFd outbuf_acquire_fence;
+ std::vector<UniqueFd> layer_acquire_fences;
+ int release_timeline;
+};
+
+class VirtualCompositorWorker : public QueueWorker<VirtualComposition> {
public:
VirtualCompositorWorker();
~VirtualCompositorWorker() override;
@@ -33,20 +37,12 @@
void QueueComposite(hwc_display_contents_1_t *dc);
protected:
- void Routine() override;
+ void ProcessWork(std::unique_ptr<VirtualComposition> composition);
private:
- struct VirtualComposition {
- UniqueFd outbuf_acquire_fence;
- std::vector<UniqueFd> layer_acquire_fences;
- int release_timeline;
- };
-
int CreateNextTimelineFence();
int FinishComposition(int timeline);
- void Compose(std::unique_ptr<VirtualComposition> composition);
- std::queue<std::unique_ptr<VirtualComposition>> composite_queue_;
int timeline_fd_;
int timeline_;
int timeline_current_;