drm_hwcomposer: add scene flattening
Flattening of a scene is triggered if it doesn't change for a while.
As of now there is a separate thread which triggers flattening if the
scene did not change in last 60 vsyncs.
There are two options for flattening a scene:
* Serial, by using a writeback connector attached to the same crtc as
the one driving the display. This happens only if possible_clones
mask reports that the display encoder and writeback encoder could
work simultaneously.
The steps for achieving this are:
1. Build a commit that enables writeback connector, we don't need to
build a commit that contains the entire active_composition, just
set the writeback specific properties a let the kernel duplicate
the rest of the state.
2. Commit and wait for writeback_fence to fire.
3. Setup a composition with just one plane enabled.
4. Apply the composition if a new one has not been applied meanwhile.
* Concurrent, by comitting the scene to a new unused crtc (state !=
DRM_MODE_CONNECTED) and getting the result back through a writeback
connector.
The steps for achieving this are:
1. Copy layers from active composition.
2. Plan layers of copy on the unused crtc. This is realized by using a
newly created DrmDisplayCompositor object.
3. Commit copy to the unsed crtc and get the result as a drmhwclayer.
4. Setup a composition with just one plane enabled. Re-importing the
buffers might be needed since we might have been using a different
dri node.
5. Apply the composition if a new one has not been applied while doing
the flattening
Signed-off-by: Alexandru Gheorghe <alexandru-cosmin.gheorghe@arm.com>
diff --git a/drmdisplaycompositor.cpp b/drmdisplaycompositor.cpp
index 05832cb..7427729 100644
--- a/drmdisplaycompositor.cpp
+++ b/drmdisplaycompositor.cpp
@@ -36,8 +36,24 @@
#include "drmdevice.h"
#include "drmplane.h"
+static const uint32_t kWaitWritebackFence = 100; // ms
+
namespace android {
+class CompositorVsyncCallback : public VsyncCallback {
+ public:
+ CompositorVsyncCallback(DrmDisplayCompositor *compositor)
+ : compositor_(compositor) {
+ }
+
+ void Callback(int display, int64_t timestamp) {
+ compositor_->Vsync(display, timestamp);
+ }
+
+ private:
+ DrmDisplayCompositor *compositor_;
+};
+
DrmDisplayCompositor::DrmDisplayCompositor()
: resource_manager_(NULL),
display_(-1),
@@ -45,7 +61,9 @@
active_(false),
use_hw_overlays_(true),
dump_frames_composited_(0),
- dump_last_timestamp_ns_(0) {
+ dump_last_timestamp_ns_(0),
+ flatten_countdown_(FLATTEN_COUNTDOWN_INIT),
+ writeback_fence_(-1) {
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts))
return;
@@ -56,6 +74,7 @@
if (!initialized_)
return;
+ vsync_worker_.Exit();
int ret = pthread_mutex_lock(&lock_);
if (ret)
ALOGE("Failed to acquire compositor lock %d", ret);
@@ -77,7 +96,8 @@
int DrmDisplayCompositor::Init(ResourceManager *resource_manager, int display) {
resource_manager_ = resource_manager;
display_ = display;
- if (!resource_manager_->GetDrmDevice(display)) {
+ DrmDevice *drm = resource_manager_->GetDrmDevice(display);
+ if (!drm) {
ALOGE("Could not find drmdevice for display");
return -EINVAL;
}
@@ -86,6 +106,11 @@
ALOGE("Failed to initialize drm compositor lock %d\n", ret);
return ret;
}
+ planner_ = Planner::CreateInstance(drm);
+
+ vsync_worker_.Init(drm, display_);
+ auto callback = std::make_shared<CompositorVsyncCallback>(this);
+ vsync_worker_.RegisterCallback(callback);
initialized_ = true;
return 0;
@@ -96,6 +121,28 @@
return std::unique_ptr<DrmDisplayComposition>(new DrmDisplayComposition());
}
+std::unique_ptr<DrmDisplayComposition>
+DrmDisplayCompositor::CreateInitializedComposition() const {
+ DrmDevice *drm = resource_manager_->GetDrmDevice(display_);
+ DrmCrtc *crtc = drm->GetCrtcForDisplay(display_);
+ if (!crtc) {
+ ALOGE("Failed to find crtc for display = %d", display_);
+ return std::unique_ptr<DrmDisplayComposition>();
+ }
+ std::unique_ptr<DrmDisplayComposition> comp = CreateComposition();
+ std::shared_ptr<Importer> importer = resource_manager_->GetImporter(display_);
+ if (!importer) {
+ ALOGE("Failed to find resources for display = %d", display_);
+ return std::unique_ptr<DrmDisplayComposition>();
+ }
+ int ret = comp->Init(drm, crtc, importer.get(), planner_.get(), 0);
+ if (ret) {
+ ALOGE("Failed to init composition for display = %d", display_);
+ return std::unique_ptr<DrmDisplayComposition>();
+ }
+ return comp;
+}
+
std::tuple<uint32_t, uint32_t, int>
DrmDisplayCompositor::GetActiveModeResolution() {
DrmDevice *drm = resource_manager_->GetDrmDevice(display_);
@@ -144,8 +191,49 @@
return 0;
}
+int DrmDisplayCompositor::SetupWritebackCommit(drmModeAtomicReqPtr pset,
+ uint32_t crtc_id,
+ DrmConnector *writeback_conn,
+ DrmHwcBuffer *writeback_buffer) {
+ int ret = 0;
+ if (writeback_conn->writeback_fb_id().id() == 0 ||
+ writeback_conn->writeback_out_fence().id() == 0) {
+ ALOGE("Writeback properties don't exit");
+ return -EINVAL;
+ }
+ if ((*writeback_buffer)->fb_id == 0) {
+ ALOGE("Invalid writeback buffer");
+ return -EINVAL;
+ }
+ ret = drmModeAtomicAddProperty(pset, writeback_conn->id(),
+ writeback_conn->writeback_fb_id().id(),
+ (*writeback_buffer)->fb_id);
+ if (ret < 0) {
+ ALOGE("Failed to add writeback_fb_id");
+ return ret;
+ }
+ ret = drmModeAtomicAddProperty(pset, writeback_conn->id(),
+ writeback_conn->writeback_out_fence().id(),
+ (uint64_t)&writeback_fence_);
+ if (ret < 0) {
+ ALOGE("Failed to add writeback_out_fence");
+ return ret;
+ }
+
+ ret = drmModeAtomicAddProperty(pset, writeback_conn->id(),
+ writeback_conn->crtc_id_property().id(),
+ crtc_id);
+ if (ret < 0) {
+ ALOGE("Failed to attach writeback");
+ return ret;
+ }
+ return 0;
+}
+
int DrmDisplayCompositor::CommitFrame(DrmDisplayComposition *display_comp,
- bool test_only) {
+ bool test_only,
+ DrmConnector *writeback_conn,
+ DrmHwcBuffer *writeback_buffer) {
ATRACE_CALL();
int ret = 0;
@@ -173,6 +261,18 @@
return -ENOMEM;
}
+ if (writeback_buffer != NULL) {
+ if (writeback_conn == NULL) {
+ ALOGE("Invalid arguments requested writeback without writeback conn");
+ return -EINVAL;
+ }
+ ret = SetupWritebackCommit(pset, crtc->id(), writeback_conn,
+ writeback_buffer);
+ if (ret < 0) {
+ ALOGE("Failed to Setup Writeback Commit ret = %d", ret);
+ return ret;
+ }
+ }
if (crtc->out_fence_ptr_property().id() != 0) {
ret = drmModeAtomicAddProperty(pset, crtc->id(), crtc->out_fence_ptr_property().id(),
(uint64_t) &out_fences[crtc->pipe()]);
@@ -438,17 +538,24 @@
return;
active_composition_.reset(NULL);
+ vsync_worker_.VSyncControl(false);
}
void DrmDisplayCompositor::ApplyFrame(
- std::unique_ptr<DrmDisplayComposition> composition, int status) {
+ std::unique_ptr<DrmDisplayComposition> composition, int status,
+ bool writeback) {
AutoLock lock(&lock_, __func__);
if (lock.Lock())
return;
int ret = status;
- if (!ret)
+ if (!ret) {
+ if (writeback && !CountdownExpired()) {
+ ALOGE("Abort playing back scene");
+ return;
+ }
ret = CommitFrame(composition.get(), false);
+ }
if (ret) {
ALOGE("Composite failed for display %d", display_);
@@ -461,6 +568,8 @@
active_composition_.swap(composition);
+ flatten_countdown_ = FLATTEN_COUNTDOWN_INIT;
+ vsync_worker_.VSyncControl(!writeback);
}
int DrmDisplayCompositor::ApplyComposition(
@@ -510,6 +619,336 @@
return CommitFrame(composition, true);
}
+// Flatten a scene on the display by using a writeback connector
+// and returns the composition result as a DrmHwcLayer.
+int DrmDisplayCompositor::FlattenOnDisplay(
+ std::unique_ptr<DrmDisplayComposition> &src, DrmConnector *writeback_conn,
+ DrmMode &src_mode, DrmHwcLayer *writeback_layer) {
+ int ret = 0;
+ DrmDevice *drm = resource_manager_->GetDrmDevice(display_);
+ ret = writeback_conn->UpdateModes();
+ if (ret) {
+ ALOGE("Failed to update modes %d", ret);
+ return ret;
+ }
+ for (const DrmMode &mode : writeback_conn->modes()) {
+ if (mode.h_display() == src_mode.h_display() &&
+ mode.v_display() == src_mode.v_display()) {
+ mode_.mode = mode;
+ if (mode_.blob_id)
+ drm->DestroyPropertyBlob(mode_.blob_id);
+ std::tie(ret, mode_.blob_id) = CreateModeBlob(mode_.mode);
+ if (ret) {
+ ALOGE("Failed to create mode blob for display %d", display_);
+ return ret;
+ }
+ mode_.needs_modeset = true;
+ break;
+ }
+ }
+ if (mode_.blob_id <= 0) {
+ ALOGE("Failed to find similar mode");
+ return -EINVAL;
+ }
+
+ DrmCrtc *crtc = drm->GetCrtcForDisplay(display_);
+ if (!crtc) {
+ ALOGE("Failed to find crtc for display %d", display_);
+ return -EINVAL;
+ }
+ // TODO what happens if planes could go to both CRTCs, I don't think it's
+ // handled anywhere
+ std::vector<DrmPlane *> primary_planes;
+ std::vector<DrmPlane *> overlay_planes;
+ for (auto &plane : drm->planes()) {
+ if (!plane->GetCrtcSupported(*crtc))
+ continue;
+ if (plane->type() == DRM_PLANE_TYPE_PRIMARY)
+ primary_planes.push_back(plane.get());
+ else if (plane->type() == DRM_PLANE_TYPE_OVERLAY)
+ overlay_planes.push_back(plane.get());
+ }
+
+ ret = src->Plan(&primary_planes, &overlay_planes);
+ if (ret) {
+ ALOGE("Failed to plan the composition ret = %d", ret);
+ return ret;
+ }
+
+ // Disable the planes we're not using
+ for (auto i = primary_planes.begin(); i != primary_planes.end();) {
+ src->AddPlaneDisable(*i);
+ i = primary_planes.erase(i);
+ }
+ for (auto i = overlay_planes.begin(); i != overlay_planes.end();) {
+ src->AddPlaneDisable(*i);
+ i = overlay_planes.erase(i);
+ }
+
+ AutoLock lock(&lock_, __func__);
+ ret = lock.Lock();
+ if (ret)
+ return ret;
+ DrmFramebuffer *writeback_fb = &framebuffers_[framebuffer_index_];
+ framebuffer_index_ = (framebuffer_index_ + 1) % DRM_DISPLAY_BUFFERS;
+ if (!writeback_fb->Allocate(mode_.mode.h_display(), mode_.mode.v_display())) {
+ ALOGE("Failed to allocate writeback buffer");
+ return -ENOMEM;
+ }
+ DrmHwcBuffer *writeback_buffer = &writeback_layer->buffer;
+ writeback_layer->sf_handle = writeback_fb->buffer()->handle;
+ ret = writeback_layer->ImportBuffer(
+ resource_manager_->GetImporter(display_).get());
+ if (ret) {
+ ALOGE("Failed to import writeback buffer");
+ return ret;
+ }
+
+ ret = CommitFrame(src.get(), true, writeback_conn, writeback_buffer);
+ if (ret) {
+ ALOGE("Atomic check failed");
+ return ret;
+ }
+ ret = CommitFrame(src.get(), false, writeback_conn, writeback_buffer);
+ if (ret) {
+ ALOGE("Atomic commit failed");
+ return ret;
+ }
+
+ ret = sync_wait(writeback_fence_, kWaitWritebackFence);
+ writeback_layer->acquire_fence.Set(writeback_fence_);
+ writeback_fence_ = -1;
+ if (ret) {
+ ALOGE("Failed to wait on writeback fence");
+ return ret;
+ }
+ return 0;
+}
+
+// Flatten a scene by enabling the writeback connector attached
+// to the same CRTC as the one driving the display.
+int DrmDisplayCompositor::FlattenSerial(DrmConnector *writeback_conn) {
+ ALOGV("FlattenSerial by enabling writeback connector to the same crtc");
+ // Flattened composition with only one layer that is obtained
+ // using the writeback connector
+ std::unique_ptr<DrmDisplayComposition> writeback_comp =
+ CreateInitializedComposition();
+ if (!writeback_comp)
+ return -EINVAL;
+
+ AutoLock lock(&lock_, __func__);
+ int ret = lock.Lock();
+ if (ret)
+ return ret;
+ if (!CountdownExpired() || active_composition_->layers().size() < 2) {
+ ALOGV("Flattening is not needed");
+ return -EALREADY;
+ }
+
+ DrmFramebuffer *writeback_fb = &framebuffers_[framebuffer_index_];
+ framebuffer_index_ = (framebuffer_index_ + 1) % DRM_DISPLAY_BUFFERS;
+ lock.Unlock();
+
+ if (!writeback_fb->Allocate(mode_.mode.h_display(), mode_.mode.v_display())) {
+ ALOGE("Failed to allocate writeback buffer");
+ return -ENOMEM;
+ }
+ writeback_comp->layers().emplace_back();
+
+ DrmHwcLayer &writeback_layer = writeback_comp->layers().back();
+ writeback_layer.sf_handle = writeback_fb->buffer()->handle;
+ writeback_layer.source_crop = {0, 0, (float)mode_.mode.h_display(),
+ (float)mode_.mode.v_display()};
+ writeback_layer.display_frame = {0, 0, (int)mode_.mode.h_display(),
+ (int)mode_.mode.v_display()};
+ ret = writeback_layer.ImportBuffer(
+ resource_manager_->GetImporter(display_).get());
+ if (ret || writeback_comp->layers().size() != 1) {
+ ALOGE("Failed to import writeback buffer");
+ return ret;
+ }
+
+ drmModeAtomicReqPtr pset = drmModeAtomicAlloc();
+ if (!pset) {
+ ALOGE("Failed to allocate property set");
+ return -ENOMEM;
+ }
+ DrmDevice *drm = resource_manager_->GetDrmDevice(display_);
+ DrmCrtc *crtc = drm->GetCrtcForDisplay(display_);
+ if (!crtc) {
+ ALOGE("Failed to find crtc for display %d", display_);
+ return -EINVAL;
+ }
+ ret = SetupWritebackCommit(pset, crtc->id(), writeback_conn,
+ &writeback_layer.buffer);
+ if (ret < 0) {
+ ALOGE("Failed to Setup Writeback Commit");
+ return ret;
+ }
+ ret = drmModeAtomicCommit(drm->fd(), pset, 0, drm);
+ if (ret) {
+ ALOGE("Failed to enable writeback %d", ret);
+ return ret;
+ }
+ ret = sync_wait(writeback_fence_, kWaitWritebackFence);
+ writeback_layer.acquire_fence.Set(writeback_fence_);
+ writeback_fence_ = -1;
+ if (ret) {
+ ALOGE("Failed to wait on writeback fence");
+ return ret;
+ }
+
+ DrmCompositionPlane squashed_comp(DrmCompositionPlane::Type::kLayer, NULL,
+ crtc);
+ for (auto &drmplane : drm->planes()) {
+ if (!drmplane->GetCrtcSupported(*crtc))
+ continue;
+ if (!squashed_comp.plane() && drmplane->type() == DRM_PLANE_TYPE_PRIMARY)
+ squashed_comp.set_plane(drmplane.get());
+ else
+ writeback_comp->AddPlaneDisable(drmplane.get());
+ }
+ squashed_comp.source_layers().push_back(0);
+ ret = writeback_comp->AddPlaneComposition(std::move(squashed_comp));
+ if (ret) {
+ ALOGE("Failed to add flatten scene");
+ return ret;
+ }
+
+ ApplyFrame(std::move(writeback_comp), 0, true);
+ return 0;
+}
+
+// Flatten a scene by using a crtc which works concurrent with
+// the one driving the display.
+int DrmDisplayCompositor::FlattenConcurrent(DrmConnector *writeback_conn) {
+ ALOGV("FlattenConcurrent by using an unused crtc/display");
+ int ret = 0;
+ DrmDisplayCompositor drmdisplaycompositor;
+ ret = drmdisplaycompositor.Init(resource_manager_, writeback_conn->display());
+ if (ret) {
+ ALOGE("Failed to init drmdisplaycompositor = %d", ret);
+ return ret;
+ }
+ // Copy of the active_composition, needed because of two things:
+ // 1) Not to hold the lock for the whole time we are accessing
+ // active_composition
+ // 2) It will be committed on a crtc that might not be on the same
+ // dri node, so buffers need to be imported on the right node.
+ std::unique_ptr<DrmDisplayComposition> copy_comp =
+ drmdisplaycompositor.CreateInitializedComposition();
+
+ // Writeback composition that will be committed to the display.
+ std::unique_ptr<DrmDisplayComposition> writeback_comp =
+ CreateInitializedComposition();
+
+ if (!copy_comp || !writeback_comp)
+ return -EINVAL;
+ AutoLock lock(&lock_, __func__);
+ ret = lock.Lock();
+ if (ret)
+ return ret;
+ if (!CountdownExpired() || active_composition_->layers().size() < 2) {
+ ALOGV("Flattening is not needed");
+ return -EALREADY;
+ }
+ DrmCrtc *crtc = active_composition_->crtc();
+
+ std::vector<DrmHwcLayer> copy_layers;
+ for (DrmHwcLayer &src_layer : active_composition_->layers()) {
+ DrmHwcLayer copy;
+ ret = copy.InitFromDrmHwcLayer(
+ &src_layer,
+ resource_manager_->GetImporter(writeback_conn->display()).get());
+ if (ret) {
+ ALOGE("Failed to import buffer ret = %d", ret);
+ return -EINVAL;
+ }
+ copy_layers.emplace_back(std::move(copy));
+ }
+ ret = copy_comp->SetLayers(copy_layers.data(), copy_layers.size(), true);
+ if (ret) {
+ ALOGE("Failed to set copy_comp layers");
+ return ret;
+ }
+
+ lock.Unlock();
+ DrmHwcLayer writeback_layer;
+ ret = drmdisplaycompositor.FlattenOnDisplay(copy_comp, writeback_conn,
+ mode_.mode, &writeback_layer);
+ if (ret) {
+ ALOGE("Failed to flatten on display ret = %d", ret);
+ return ret;
+ }
+
+ DrmCompositionPlane squashed_comp(DrmCompositionPlane::Type::kLayer, NULL,
+ crtc);
+ for (auto &drmplane : resource_manager_->GetDrmDevice(display_)->planes()) {
+ if (!drmplane->GetCrtcSupported(*crtc))
+ continue;
+ if (drmplane->type() == DRM_PLANE_TYPE_PRIMARY)
+ squashed_comp.set_plane(drmplane.get());
+ else
+ writeback_comp->AddPlaneDisable(drmplane.get());
+ }
+ writeback_comp->layers().emplace_back();
+ DrmHwcLayer &next_layer = writeback_comp->layers().back();
+ next_layer.sf_handle = writeback_layer.get_usable_handle();
+ next_layer.blending = DrmHwcBlending::kPreMult;
+ next_layer.source_crop = {0, 0, (float)mode_.mode.h_display(),
+ (float)mode_.mode.v_display()};
+ next_layer.display_frame = {0, 0, (int)mode_.mode.h_display(),
+ (int)mode_.mode.v_display()};
+ ret = next_layer.ImportBuffer(resource_manager_->GetImporter(display_).get());
+ if (ret) {
+ ALOGE("Failed to import framebuffer for display %d", ret);
+ return ret;
+ }
+ squashed_comp.source_layers().push_back(0);
+ ret = writeback_comp->AddPlaneComposition(std::move(squashed_comp));
+ if (ret) {
+ ALOGE("Failed to add plane composition %d", ret);
+ return ret;
+ }
+ ApplyFrame(std::move(writeback_comp), 0, true);
+ return ret;
+}
+
+int DrmDisplayCompositor::FlattenActiveComposition() {
+ DrmConnector *writeback_conn =
+ resource_manager_->AvailableWritebackConnector(display_);
+ if (!active_composition_ || !writeback_conn) {
+ ALOGV("No writeback connector available");
+ return -EINVAL;
+ }
+
+ if (writeback_conn->display() != display_) {
+ return FlattenConcurrent(writeback_conn);
+ } else {
+ return FlattenSerial(writeback_conn);
+ }
+
+ return 0;
+}
+
+bool DrmDisplayCompositor::CountdownExpired() const {
+ return flatten_countdown_ <= 0;
+}
+
+void DrmDisplayCompositor::Vsync(int display, int64_t timestamp) {
+ AutoLock lock(&lock_, __func__);
+ if (lock.Lock())
+ return;
+ flatten_countdown_--;
+ if (!CountdownExpired())
+ return;
+ lock.Unlock();
+ int ret = FlattenActiveComposition();
+ ALOGV("scene flattening triggered for display %d at timestamp %" PRIu64
+ " result = %d \n",
+ display, timestamp, ret);
+}
+
void DrmDisplayCompositor::Dump(std::ostringstream *out) const {
int ret = pthread_mutex_lock(&lock_);
if (ret)